@simtlix/simfinity-js 1.7.0 → 1.9.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
|
@@ -8,6 +8,7 @@ A powerful Node.js framework that automatically generates GraphQL schemas from y
|
|
|
8
8
|
- **MongoDB Integration**: Seamless translation between GraphQL and MongoDB
|
|
9
9
|
- **Powerful Querying**: Any query that can be executed in MongoDB can be executed in GraphQL
|
|
10
10
|
- **Auto-Generated Resolvers**: Automatically generates resolve methods for relationship fields
|
|
11
|
+
- **Automatic Index Creation**: Automatically creates MongoDB indexes for all ObjectId fields, including nested embedded objects and relationship fields
|
|
11
12
|
- **Business Logic**: Implement business logic and domain validations declaratively
|
|
12
13
|
- **State Machines**: Built-in support for declarative state machine workflows
|
|
13
14
|
- **Lifecycle Hooks**: Controller methods for granular control over operations
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Automatic ObjectId Index Creation
|
|
2
|
+
|
|
3
|
+
This feature automatically creates MongoDB indexes for all ObjectId fields when models are generated, including nested embedded objects.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
When you define GraphQL types with ObjectId fields (GraphQLID), the system will automatically:
|
|
8
|
+
|
|
9
|
+
1. Analyze the schema definition to find all ObjectId fields
|
|
10
|
+
2. Create indexes for each ObjectId field found
|
|
11
|
+
3. Handle nested embedded objects recursively
|
|
12
|
+
4. Create indexes for arrays of embedded objects
|
|
13
|
+
5. Create indexes for relationship fields (foreign keys between types)
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### Simple ObjectId fields
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { GraphQLObjectType, GraphQLString, GraphQLID } from 'graphql';
|
|
21
|
+
import * as simfinity from '@simtlix/simfinity-js';
|
|
22
|
+
|
|
23
|
+
const UserType = new GraphQLObjectType({
|
|
24
|
+
name: 'User',
|
|
25
|
+
fields: () => ({
|
|
26
|
+
id: { type: GraphQLID },
|
|
27
|
+
name: { type: GraphQLString },
|
|
28
|
+
email: { type: GraphQLString },
|
|
29
|
+
managerId: { type: GraphQLID }, // This will get an index
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
34
|
+
simfinity.createSchema();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This will create indexes for:
|
|
38
|
+
- `id` field
|
|
39
|
+
- `managerId` field
|
|
40
|
+
|
|
41
|
+
### Relationship fields (non-embedded)
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const DepartmentType = new GraphQLObjectType({
|
|
45
|
+
name: 'Department',
|
|
46
|
+
fields: () => ({
|
|
47
|
+
id: { type: GraphQLID },
|
|
48
|
+
name: { type: GraphQLString },
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const UserType = new GraphQLObjectType({
|
|
53
|
+
name: 'User',
|
|
54
|
+
fields: () => ({
|
|
55
|
+
id: { type: GraphQLID },
|
|
56
|
+
name: { type: GraphQLString },
|
|
57
|
+
email: { type: GraphQLString },
|
|
58
|
+
department: {
|
|
59
|
+
type: DepartmentType,
|
|
60
|
+
extensions: {
|
|
61
|
+
relation: { embedded: false } // or omit this line (default is false)
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Register both types
|
|
68
|
+
simfinity.connect(null, DepartmentType, 'department', 'departments');
|
|
69
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
70
|
+
simfinity.createSchema();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This will create indexes for:
|
|
74
|
+
- `id` field (in both User and Department)
|
|
75
|
+
- `department` field (the foreign key field that references Department)
|
|
76
|
+
|
|
77
|
+
### One-to-Many relationships
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const OrderType = new GraphQLObjectType({
|
|
81
|
+
name: 'Order',
|
|
82
|
+
fields: () => ({
|
|
83
|
+
id: { type: GraphQLID },
|
|
84
|
+
orderNumber: { type: GraphQLString },
|
|
85
|
+
customerId: { type: GraphQLID }, // This will get an index
|
|
86
|
+
items: {
|
|
87
|
+
type: new GraphQLList(OrderItemType),
|
|
88
|
+
extensions: {
|
|
89
|
+
relation: { embedded: false }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const OrderItemType = new GraphQLObjectType({
|
|
96
|
+
name: 'OrderItem',
|
|
97
|
+
fields: () => ({
|
|
98
|
+
id: { type: GraphQLID },
|
|
99
|
+
productId: { type: GraphQLID }, // This will get an index
|
|
100
|
+
quantity: { type: GraphQLString },
|
|
101
|
+
orderId: { type: GraphQLID }, // This will get an index (foreign key)
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Register both types
|
|
106
|
+
simfinity.connect(null, OrderItemType, 'orderItem', 'orderItems');
|
|
107
|
+
simfinity.connect(null, OrderType, 'order', 'orders');
|
|
108
|
+
simfinity.createSchema();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This will create indexes for:
|
|
112
|
+
- `id` field (in both Order and OrderItem)
|
|
113
|
+
- `customerId` field (in Order)
|
|
114
|
+
- `productId` field (in OrderItem)
|
|
115
|
+
- `orderId` field (in OrderItem - the foreign key that references Order)
|
|
116
|
+
|
|
117
|
+
### Embedded objects with ObjectId
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
const AddressType = new GraphQLObjectType({
|
|
121
|
+
name: 'Address',
|
|
122
|
+
fields: () => ({
|
|
123
|
+
street: { type: GraphQLString },
|
|
124
|
+
city: { type: GraphQLString },
|
|
125
|
+
countryId: { type: GraphQLID }, // This will get an index
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const UserType = new GraphQLObjectType({
|
|
130
|
+
name: 'User',
|
|
131
|
+
fields: () => ({
|
|
132
|
+
id: { type: GraphQLID },
|
|
133
|
+
name: { type: GraphQLString },
|
|
134
|
+
address: {
|
|
135
|
+
type: AddressType,
|
|
136
|
+
extensions: {
|
|
137
|
+
relation: { embedded: true }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Register the embedded type
|
|
144
|
+
simfinity.addNoEndpointType(AddressType);
|
|
145
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
146
|
+
simfinity.createSchema();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This will create indexes for:
|
|
150
|
+
- `id` field
|
|
151
|
+
- `countryId` field (from AddressType)
|
|
152
|
+
- `address.countryId` field (embedded path)
|
|
153
|
+
|
|
154
|
+
### Nested embedded objects
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const CityType = new GraphQLObjectType({
|
|
158
|
+
name: 'City',
|
|
159
|
+
fields: () => ({
|
|
160
|
+
name: { type: GraphQLString },
|
|
161
|
+
stateId: { type: GraphQLID }, // This will get an index
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const AddressType = new GraphQLObjectType({
|
|
166
|
+
name: 'Address',
|
|
167
|
+
fields: () => ({
|
|
168
|
+
street: { type: GraphQLString },
|
|
169
|
+
city: {
|
|
170
|
+
type: CityType,
|
|
171
|
+
extensions: {
|
|
172
|
+
relation: { embedded: true }
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const UserType = new GraphQLObjectType({
|
|
179
|
+
name: 'User',
|
|
180
|
+
fields: () => ({
|
|
181
|
+
id: { type: GraphQLID },
|
|
182
|
+
name: { type: GraphQLString },
|
|
183
|
+
address: {
|
|
184
|
+
type: AddressType,
|
|
185
|
+
extensions: {
|
|
186
|
+
relation: { embedded: true }
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Register the embedded types
|
|
193
|
+
simfinity.addNoEndpointType(CityType);
|
|
194
|
+
simfinity.addNoEndpointType(AddressType);
|
|
195
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
196
|
+
simfinity.createSchema();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This will create indexes for:
|
|
200
|
+
- `id` field
|
|
201
|
+
- `stateId` field (from CityType)
|
|
202
|
+
- `city.stateId` field (from AddressType)
|
|
203
|
+
- `address.city.stateId` field (nested embedded path)
|
|
204
|
+
|
|
205
|
+
### Arrays of embedded objects
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
const OrderItemType = new GraphQLObjectType({
|
|
209
|
+
name: 'OrderItem',
|
|
210
|
+
fields: () => ({
|
|
211
|
+
productId: { type: GraphQLID }, // This will get an index
|
|
212
|
+
quantity: { type: GraphQLString },
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const OrderType = new GraphQLObjectType({
|
|
217
|
+
name: 'Order',
|
|
218
|
+
fields: () => ({
|
|
219
|
+
id: { type: GraphQLID },
|
|
220
|
+
customerId: { type: GraphQLID },
|
|
221
|
+
items: {
|
|
222
|
+
type: new GraphQLList(OrderItemType),
|
|
223
|
+
extensions: {
|
|
224
|
+
relation: { embedded: true }
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Register the embedded type
|
|
231
|
+
simfinity.addNoEndpointType(OrderItemType);
|
|
232
|
+
simfinity.connect(null, OrderType, 'order', 'orders');
|
|
233
|
+
simfinity.createSchema();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
This will create indexes for:
|
|
237
|
+
- `id` field
|
|
238
|
+
- `customerId` field
|
|
239
|
+
- `productId` field (from OrderItemType)
|
|
240
|
+
- `items.productId` field (array embedded path)
|
|
241
|
+
|
|
242
|
+
## Benefits
|
|
243
|
+
|
|
244
|
+
1. **Automatic Performance Optimization**: ObjectId fields are automatically indexed for better query performance
|
|
245
|
+
2. **Relationship Optimization**: Foreign key fields are automatically indexed for efficient joins and lookups
|
|
246
|
+
3. **No Manual Configuration**: No need to manually specify which fields should be indexed
|
|
247
|
+
4. **Handles Complex Schemas**: Works with nested embedded objects and arrays
|
|
248
|
+
5. **Consistent**: All ObjectId fields get the same treatment regardless of their location in the schema
|
|
249
|
+
|
|
250
|
+
## Technical Details
|
|
251
|
+
|
|
252
|
+
The system uses the `mongoose.Schema` constructor to create schemas and then calls `schema.index()` for each ObjectId field found. The index creation happens during model generation, so it's transparent to the application code.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1003,8 +1003,56 @@ const generateSchemaDefinition = (gqlType) => {
|
|
|
1003
1003
|
return schemaArg;
|
|
1004
1004
|
};
|
|
1005
1005
|
|
|
1006
|
+
const findObjectIdFields = (schemaDefinition, parentPath = '') => {
|
|
1007
|
+
const objectIdFields = [];
|
|
1008
|
+
|
|
1009
|
+
for (const [fieldName, fieldDefinition] of Object.entries(schemaDefinition)) {
|
|
1010
|
+
const currentPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
1011
|
+
|
|
1012
|
+
if (fieldDefinition === mongoose.Schema.Types.ObjectId) {
|
|
1013
|
+
// Direct ObjectId field
|
|
1014
|
+
objectIdFields.push(currentPath);
|
|
1015
|
+
} else if (typeof fieldDefinition === 'object' && fieldDefinition !== null) {
|
|
1016
|
+
if (Array.isArray(fieldDefinition)) {
|
|
1017
|
+
// Array field - check if it's an array of objects
|
|
1018
|
+
const arrayElement = fieldDefinition[0];
|
|
1019
|
+
if (typeof arrayElement === 'object' && arrayElement !== null) {
|
|
1020
|
+
// Array of embedded objects - recursively check for ObjectId fields
|
|
1021
|
+
const nestedObjectIdFields = findObjectIdFields(arrayElement, currentPath);
|
|
1022
|
+
objectIdFields.push(...nestedObjectIdFields);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (fieldDefinition.type === mongoose.Schema.Types.ObjectId) {
|
|
1025
|
+
// Object with ObjectId type
|
|
1026
|
+
objectIdFields.push(currentPath);
|
|
1027
|
+
} else if (typeof fieldDefinition === 'object' && !fieldDefinition.type) {
|
|
1028
|
+
// Embedded object - recursively check for ObjectId fields
|
|
1029
|
+
const nestedObjectIdFields = findObjectIdFields(fieldDefinition, currentPath);
|
|
1030
|
+
objectIdFields.push(...nestedObjectIdFields);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return objectIdFields;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
const createSchemaWithIndexes = (schemaDefinition) => {
|
|
1039
|
+
const schema = new mongoose.Schema(schemaDefinition);
|
|
1040
|
+
|
|
1041
|
+
// Find all ObjectId fields in the schema
|
|
1042
|
+
const objectIdFields = findObjectIdFields(schemaDefinition);
|
|
1043
|
+
|
|
1044
|
+
// Create indexes for all ObjectId fields
|
|
1045
|
+
objectIdFields.forEach(fieldPath => {
|
|
1046
|
+
schema.index({ [fieldPath]: 1 });
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
return schema;
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1006
1052
|
const generateModel = (gqlType, onModelCreated) => {
|
|
1007
|
-
const
|
|
1053
|
+
const schemaDefinition = generateSchemaDefinition(gqlType);
|
|
1054
|
+
const schema = createSchemaWithIndexes(schemaDefinition);
|
|
1055
|
+
const model = mongoose.model(gqlType.name, schema, gqlType.name);
|
|
1008
1056
|
if (onModelCreated) {
|
|
1009
1057
|
onModelCreated(model);
|
|
1010
1058
|
}
|
|
@@ -1015,7 +1063,9 @@ const generateModel = (gqlType, onModelCreated) => {
|
|
|
1015
1063
|
};
|
|
1016
1064
|
|
|
1017
1065
|
const generateModelWithoutCollection = (gqlType, onModelCreated) => {
|
|
1018
|
-
const
|
|
1066
|
+
const schemaDefinition = generateSchemaDefinition(gqlType);
|
|
1067
|
+
const schema = createSchemaWithIndexes(schemaDefinition);
|
|
1068
|
+
const model = mongoose.model(gqlType.name, schema, gqlType.name);
|
|
1019
1069
|
if (onModelCreated) {
|
|
1020
1070
|
onModelCreated(model);
|
|
1021
1071
|
}
|
|
@@ -1537,6 +1587,26 @@ we will allow users to use when they are making request. */
|
|
|
1537
1587
|
export const createSchema = (includedQueryTypes,
|
|
1538
1588
|
includedMutationTypes, includedCustomMutations) => {
|
|
1539
1589
|
|
|
1590
|
+
// Generate models for all registered types now that all types are available
|
|
1591
|
+
Object.values(typesDict.types).forEach(typeInfo => {
|
|
1592
|
+
if (typeInfo.gqltype && !typeInfo.model) {
|
|
1593
|
+
if (typeInfo.endpoint) {
|
|
1594
|
+
// Generate model with collection for endpoint types (types registered with connect)
|
|
1595
|
+
typeInfo.model = generateModel(typeInfo.gqltype, typeInfo.onModelCreated);
|
|
1596
|
+
} else if (typeInfo.needsModel) {
|
|
1597
|
+
// Generate model without collection for no-endpoint types that need models (addNoEndpointType)
|
|
1598
|
+
typeInfo.model = generateModelWithoutCollection(typeInfo.gqltype, null);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
// Also update the typesDictForUpdate with the generated models
|
|
1604
|
+
Object.keys(typesDict.types).forEach(typeName => {
|
|
1605
|
+
if (typesDictForUpdate.types[typeName]) {
|
|
1606
|
+
typesDictForUpdate.types[typeName].model = typesDict.types[typeName].model;
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1540
1610
|
// Auto-generate resolvers for all registered types now that all types are available
|
|
1541
1611
|
Object.values(typesDict.types).forEach(typeInfo => {
|
|
1542
1612
|
if (typeInfo.gqltype) {
|
|
@@ -1628,13 +1698,14 @@ export const connect = (model, gqltype, simpleEntityEndpointName,
|
|
|
1628
1698
|
gqltype,
|
|
1629
1699
|
};
|
|
1630
1700
|
typesDict.types[gqltype.name] = {
|
|
1631
|
-
model: model
|
|
1701
|
+
model: model, // Will be generated later in createSchema if not provided
|
|
1632
1702
|
gqltype,
|
|
1633
1703
|
simpleEntityEndpointName,
|
|
1634
1704
|
listEntitiesEndpointName,
|
|
1635
1705
|
endpoint: true,
|
|
1636
1706
|
controller,
|
|
1637
1707
|
stateMachine,
|
|
1708
|
+
onModelCreated, // Store the callback for later use
|
|
1638
1709
|
};
|
|
1639
1710
|
|
|
1640
1711
|
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
@@ -1661,8 +1732,9 @@ export const addNoEndpointType = (gqltype) => {
|
|
|
1661
1732
|
typesDict.types[gqltype.name] = {
|
|
1662
1733
|
gqltype,
|
|
1663
1734
|
endpoint: false,
|
|
1664
|
-
//
|
|
1665
|
-
model:
|
|
1735
|
+
// Model will be generated later in createSchema if needed
|
|
1736
|
+
model: null,
|
|
1737
|
+
needsModel, // Store whether this type needs a model
|
|
1666
1738
|
};
|
|
1667
1739
|
|
|
1668
1740
|
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe, test, expect, beforeEach, afterEach, vi,
|
|
3
|
+
} from 'vitest';
|
|
4
|
+
import mongoose from 'mongoose';
|
|
5
|
+
import { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList } from 'graphql';
|
|
6
|
+
import * as simfinity from '../src/index.js';
|
|
7
|
+
|
|
8
|
+
describe('ObjectId Index Creation', () => {
|
|
9
|
+
let indexSpy;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Spy on the index method of mongoose Schema
|
|
13
|
+
indexSpy = vi.spyOn(mongoose.Schema.prototype, 'index').mockImplementation(() => {});
|
|
14
|
+
// Prevent collection creation to avoid database connection issues
|
|
15
|
+
simfinity.preventCreatingCollection(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Restore the original implementation
|
|
20
|
+
indexSpy.mockRestore();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should create index for direct ObjectId field', () => {
|
|
24
|
+
const TestType = new GraphQLObjectType({
|
|
25
|
+
name: 'TestTypeWithObjectId',
|
|
26
|
+
fields: () => ({
|
|
27
|
+
id: { type: GraphQLID },
|
|
28
|
+
name: { type: GraphQLString },
|
|
29
|
+
userId: { type: GraphQLID },
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
simfinity.connect(null, TestType, 'testTypeWithObjectId', 'testTypesWithObjectId');
|
|
34
|
+
simfinity.createSchema();
|
|
35
|
+
|
|
36
|
+
// Should create indexes for both id and userId fields
|
|
37
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
38
|
+
expect(indexSpy).toHaveBeenCalledWith({ userId: 1 });
|
|
39
|
+
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('should create index for embedded ObjectId field', () => {
|
|
43
|
+
const EmbeddedType = new GraphQLObjectType({
|
|
44
|
+
name: 'EmbeddedType',
|
|
45
|
+
fields: () => ({
|
|
46
|
+
embeddedId: { type: GraphQLID },
|
|
47
|
+
embeddedName: { type: GraphQLString },
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const TestType = new GraphQLObjectType({
|
|
52
|
+
name: 'TestTypeWithEmbedded',
|
|
53
|
+
fields: () => ({
|
|
54
|
+
id: { type: GraphQLID },
|
|
55
|
+
name: { type: GraphQLString },
|
|
56
|
+
embedded: {
|
|
57
|
+
type: EmbeddedType,
|
|
58
|
+
extensions: {
|
|
59
|
+
relation: { embedded: true },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Register the embedded type first
|
|
66
|
+
simfinity.addNoEndpointType(EmbeddedType);
|
|
67
|
+
simfinity.connect(null, TestType, 'testTypeWithEmbedded', 'testTypesWithEmbedded');
|
|
68
|
+
simfinity.createSchema();
|
|
69
|
+
|
|
70
|
+
// Should create indexes for id and embedded.embeddedId fields
|
|
71
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
72
|
+
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.embeddedId': 1 });
|
|
73
|
+
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should create index for nested embedded ObjectId field', () => {
|
|
77
|
+
const DeepEmbeddedType = new GraphQLObjectType({
|
|
78
|
+
name: 'DeepEmbeddedType',
|
|
79
|
+
fields: () => ({
|
|
80
|
+
deepId: { type: GraphQLID },
|
|
81
|
+
deepName: { type: GraphQLString },
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const EmbeddedType = new GraphQLObjectType({
|
|
86
|
+
name: 'EmbeddedTypeWithDeep',
|
|
87
|
+
fields: () => ({
|
|
88
|
+
embeddedId: { type: GraphQLID },
|
|
89
|
+
embeddedName: { type: GraphQLString },
|
|
90
|
+
deep: {
|
|
91
|
+
type: DeepEmbeddedType,
|
|
92
|
+
extensions: {
|
|
93
|
+
relation: { embedded: true },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const TestType = new GraphQLObjectType({
|
|
100
|
+
name: 'TestTypeWithNestedEmbedded',
|
|
101
|
+
fields: () => ({
|
|
102
|
+
id: { type: GraphQLID },
|
|
103
|
+
name: { type: GraphQLString },
|
|
104
|
+
embedded: {
|
|
105
|
+
type: EmbeddedType,
|
|
106
|
+
extensions: {
|
|
107
|
+
relation: { embedded: true },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Register the embedded types first
|
|
114
|
+
simfinity.addNoEndpointType(DeepEmbeddedType);
|
|
115
|
+
simfinity.addNoEndpointType(EmbeddedType);
|
|
116
|
+
simfinity.connect(null, TestType, 'testTypeWithNestedEmbedded', 'testTypesWithNestedEmbedded');
|
|
117
|
+
simfinity.createSchema();
|
|
118
|
+
|
|
119
|
+
// Should create indexes for all ObjectId fields at all levels
|
|
120
|
+
expect(indexSpy).toHaveBeenCalledWith({ embeddedId: 1 }); // EmbeddedType
|
|
121
|
+
expect(indexSpy).toHaveBeenCalledWith({ 'deep.deepId': 1 }); // DeepEmbeddedType
|
|
122
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // TestType
|
|
123
|
+
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.embeddedId': 1 }); // embedded field in TestType
|
|
124
|
+
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.deep.deepId': 1 }); // nested embedded field in TestType
|
|
125
|
+
expect(indexSpy).toHaveBeenCalledTimes(5);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should create index for array of embedded objects with ObjectId', () => {
|
|
129
|
+
const EmbeddedType = new GraphQLObjectType({
|
|
130
|
+
name: 'EmbeddedTypeForArray',
|
|
131
|
+
fields: () => ({
|
|
132
|
+
embeddedId: { type: GraphQLID },
|
|
133
|
+
embeddedName: { type: GraphQLString },
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const TestType = new GraphQLObjectType({
|
|
138
|
+
name: 'TestTypeWithEmbeddedArray',
|
|
139
|
+
fields: () => ({
|
|
140
|
+
id: { type: GraphQLID },
|
|
141
|
+
name: { type: GraphQLString },
|
|
142
|
+
embeddedArray: {
|
|
143
|
+
type: new GraphQLList(EmbeddedType),
|
|
144
|
+
extensions: {
|
|
145
|
+
relation: { embedded: true },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Register the embedded type first
|
|
152
|
+
simfinity.addNoEndpointType(EmbeddedType);
|
|
153
|
+
simfinity.connect(null, TestType, 'testTypeWithEmbeddedArray', 'testTypesWithEmbeddedArray');
|
|
154
|
+
simfinity.createSchema();
|
|
155
|
+
|
|
156
|
+
// Should create indexes for id and embeddedArray.embeddedId fields
|
|
157
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
158
|
+
expect(indexSpy).toHaveBeenCalledWith({ 'embeddedArray.embeddedId': 1 });
|
|
159
|
+
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should not create index for non-ObjectId fields', () => {
|
|
163
|
+
const TestType = new GraphQLObjectType({
|
|
164
|
+
name: 'TestTypeWithoutObjectId',
|
|
165
|
+
fields: () => ({
|
|
166
|
+
id: { type: GraphQLID },
|
|
167
|
+
name: { type: GraphQLString },
|
|
168
|
+
age: { type: GraphQLString },
|
|
169
|
+
active: { type: GraphQLString },
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
simfinity.connect(null, TestType, 'testTypeWithoutObjectId', 'testTypesWithoutObjectId');
|
|
174
|
+
simfinity.createSchema();
|
|
175
|
+
|
|
176
|
+
// Should only create index for id field (ObjectId), not for other fields
|
|
177
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
178
|
+
expect(indexSpy).toHaveBeenCalledTimes(1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should create index for relationship fields (non-embedded)', () => {
|
|
182
|
+
const DepartmentType = new GraphQLObjectType({
|
|
183
|
+
name: 'DepartmentType',
|
|
184
|
+
fields: () => ({
|
|
185
|
+
id: { type: GraphQLID },
|
|
186
|
+
name: { type: GraphQLString },
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const UserType = new GraphQLObjectType({
|
|
191
|
+
name: 'TestTypeWithRelationship',
|
|
192
|
+
fields: () => ({
|
|
193
|
+
id: { type: GraphQLID },
|
|
194
|
+
name: { type: GraphQLString },
|
|
195
|
+
department: {
|
|
196
|
+
type: DepartmentType,
|
|
197
|
+
extensions: {
|
|
198
|
+
relation: { embedded: false },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Register both types
|
|
205
|
+
simfinity.connect(null, DepartmentType, 'department', 'departments');
|
|
206
|
+
simfinity.connect(null, UserType, 'testTypeWithRelationship', 'testTypesWithRelationship');
|
|
207
|
+
simfinity.createSchema();
|
|
208
|
+
|
|
209
|
+
// Should create indexes for id fields in both types and the relationship field
|
|
210
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // DepartmentType
|
|
211
|
+
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // UserType
|
|
212
|
+
expect(indexSpy).toHaveBeenCalledWith({ department: 1 }); // relationship field
|
|
213
|
+
expect(indexSpy).toHaveBeenCalledTimes(3);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -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
|
|