@simtlix/simfinity-js 2.6.4 → 3.2.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/src/mcp.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from '@simtlix/simfinity-mcp';
2
+ export { default } from '@simtlix/simfinity-mcp';
@@ -0,0 +1,111 @@
1
+ import mongoose from 'mongoose';
2
+ import { getNamedType, isListType, isNonNullType } from 'graphql';
3
+
4
+ import { createMongoModel } from './models.js';
5
+ import { createMongoQueries } from './queries.js';
6
+ import { withMongoTransaction } from './transactions.js';
7
+
8
+ mongoose.set('strictQuery', false);
9
+
10
+ const withSession = (query, session) => (session ? query.session(session) : query);
11
+
12
+ export const createMongoAdapter = () => {
13
+ let queries;
14
+ const privateConnectionFields = new Map();
15
+
16
+ const addPrivateConnectionField = (typeName, fieldName) => {
17
+ if (!privateConnectionFields.has(typeName)) privateConnectionFields.set(typeName, new Set());
18
+ privateConnectionFields.get(typeName).add(fieldName);
19
+ };
20
+
21
+ const adapter = {
22
+ bind(binding) {
23
+ queries = createMongoQueries(binding);
24
+ adapter.buildQuery = queries.buildQuery;
25
+ adapter.buildFilterGroupMatch = queries.buildFilterGroupMatch;
26
+ },
27
+ prepare(registrations) {
28
+ for (const registration of registrations) {
29
+ for (const field of Object.values(registration.gqltype.getFields())) {
30
+ const relation = field.extensions?.relation;
31
+ const listType = isNonNullType(field.type) ? field.type.ofType : field.type;
32
+ if (!relation?.connectionField || relation.embedded || !isListType(listType)) continue;
33
+ const childType = getNamedType(field.type);
34
+ const childFields = childType.getFields();
35
+ const connectionExists = childFields[relation.connectionField]
36
+ || Object.entries(childFields).some(([fieldName, childField]) => {
37
+ const childRelation = childField.extensions?.relation;
38
+ return childRelation && !childRelation.embedded
39
+ && (childRelation.connectionField || fieldName) === relation.connectionField;
40
+ });
41
+ if (!connectionExists) addPrivateConnectionField(
42
+ childType.name,
43
+ relation.connectionField,
44
+ );
45
+ }
46
+ }
47
+ },
48
+ createModel(gqltype, onModelCreated, options) {
49
+ return createMongoModel(gqltype, (model) => {
50
+ for (const fieldName of privateConnectionFields.get(gqltype.name) || []) {
51
+ model.schema.add({ [fieldName]: mongoose.Schema.Types.ObjectId });
52
+ model.schema.index({ [fieldName]: 1 });
53
+ }
54
+ if (onModelCreated) onModelCreated(model);
55
+ }, options);
56
+ },
57
+ castId(value) {
58
+ return new mongoose.Types.ObjectId(value);
59
+ },
60
+ withTransaction: withMongoTransaction,
61
+ newRecord(Model, data, session) {
62
+ const record = new Model(data);
63
+ record.$session(session);
64
+ return record;
65
+ },
66
+ saveRecord(Model, record) {
67
+ return record.save();
68
+ },
69
+ toObject(record) {
70
+ return typeof record?.toObject === 'function' ? record.toObject() : record;
71
+ },
72
+ getById(Model, id, session, { projection, plain, requiredId } = {}) {
73
+ let query = withSession(requiredId == null ? Model.findById(id, projection)
74
+ : Model.findOne({ $and: [{ _id: requiredId }, { _id: id }] }, projection), session);
75
+ if (plain) query = query.lean();
76
+ return query;
77
+ },
78
+ prepareUpdate(set, unset) {
79
+ return Object.keys(unset).length > 0 ? { ...set, $unset: unset } : set;
80
+ },
81
+ update(Model, id, update, session) {
82
+ return withSession(Model.findByIdAndUpdate(id, update, { new: true }), session);
83
+ },
84
+ delete(Model, id, session) {
85
+ return withSession(Model.findByIdAndDelete(id), session);
86
+ },
87
+ async find(Model, gqltype, args, session, { requiredId } = {}) {
88
+ const pipeline = await queries.buildQuery(args, gqltype);
89
+ if (requiredId != null) pipeline.unshift({ $match: { _id: Model.schema.path('_id').cast(requiredId) } });
90
+ if (pipeline.length === 0) return withSession(Model.find({}), session);
91
+ return withSession(Model.aggregate(pipeline), session);
92
+ },
93
+ async count(Model, gqltype, args, session) {
94
+ const pipeline = await queries.buildQuery(args, gqltype, true);
95
+ const result = await withSession(Model.aggregate(pipeline), session);
96
+ return result[0] ? result[0].size : 0;
97
+ },
98
+ async aggregate(Model, gqltype, args, session) {
99
+ const pipeline = await queries.buildAggregationQuery(args, gqltype, args.aggregation);
100
+ return withSession(Model.aggregate(pipeline), session);
101
+ },
102
+ async findChildren(Model, gqltype, connectionField, parentId, args, session) {
103
+ const pipeline = await queries.buildQuery(args, gqltype);
104
+ const path = Model.schema.path(connectionField);
105
+ pipeline.unshift({ $match: { [connectionField]: path ? path.cast(parentId) : parentId } });
106
+ return withSession(Model.aggregate(pipeline), session);
107
+ },
108
+ };
109
+
110
+ return adapter;
111
+ };
@@ -0,0 +1,160 @@
1
+ import {
2
+ GraphQLBoolean,
3
+ GraphQLEnumType,
4
+ GraphQLFloat,
5
+ GraphQLID,
6
+ GraphQLInt,
7
+ GraphQLList,
8
+ GraphQLNonNull,
9
+ GraphQLObjectType,
10
+ GraphQLScalarType,
11
+ GraphQLString,
12
+ } from 'graphql';
13
+ import mongoose from 'mongoose';
14
+
15
+ const isNonNullOfType = (fieldEntryType, graphQLType) => (
16
+ fieldEntryType instanceof GraphQLNonNull && fieldEntryType.ofType instanceof graphQLType
17
+ );
18
+
19
+ const unwrapNonNull = (type) => (type instanceof GraphQLNonNull ? type.ofType : type);
20
+
21
+ const getListItemType = (type) => {
22
+ const listType = unwrapNonNull(type);
23
+ if (!(listType instanceof GraphQLList)) return null;
24
+ return unwrapNonNull(listType.ofType);
25
+ };
26
+
27
+ const isCustomValidatedScalar = (type) => (
28
+ type instanceof GraphQLScalarType && type.baseScalarType
29
+ );
30
+
31
+ const matchesScalar = (fieldType, target) => {
32
+ if (fieldType === target) return true;
33
+ if (fieldType instanceof GraphQLNonNull && fieldType.ofType === target) return true;
34
+ if (isCustomValidatedScalar(fieldType) && fieldType.baseScalarType === target) return true;
35
+ return fieldType instanceof GraphQLNonNull
36
+ && isCustomValidatedScalar(fieldType.ofType)
37
+ && fieldType.ofType.baseScalarType === target;
38
+ };
39
+
40
+ const getEffectiveTypeName = (type) => (
41
+ type instanceof GraphQLScalarType && type.baseScalarType ? type.baseScalarType.name : type.name
42
+ );
43
+
44
+ const isGraphQLisoDate = (typeName) => (
45
+ typeName === 'DateTime' || typeName === 'Date' || typeName === 'Time'
46
+ );
47
+
48
+ const listItemMatchesScalar = (listType, target) => {
49
+ const ofType = getListItemType(listType);
50
+ return ofType === target
51
+ || (isCustomValidatedScalar(ofType) && ofType.baseScalarType === target);
52
+ };
53
+
54
+ const withUnique = (fieldEntry, mongoType) => (fieldEntry.extensions && fieldEntry.extensions.unique
55
+ ? { type: mongoType, unique: true }
56
+ : mongoType);
57
+
58
+ const generateSchemaDefinition = (gqlType) => {
59
+ const argTypes = gqlType.getFields();
60
+ const schemaArg = {};
61
+
62
+ for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
63
+ const { type } = fieldEntry;
64
+
65
+ if (matchesScalar(type, GraphQLID)) {
66
+ schemaArg[fieldEntryName] = mongoose.Schema.Types.ObjectId;
67
+ } else if (matchesScalar(type, GraphQLString)
68
+ || type instanceof GraphQLEnumType
69
+ || isNonNullOfType(type, GraphQLEnumType)) {
70
+ schemaArg[fieldEntryName] = withUnique(fieldEntry, String);
71
+ } else if (matchesScalar(type, GraphQLInt) || matchesScalar(type, GraphQLFloat)) {
72
+ schemaArg[fieldEntryName] = withUnique(fieldEntry, Number);
73
+ } else if (matchesScalar(type, GraphQLBoolean)) {
74
+ schemaArg[fieldEntryName] = Boolean;
75
+ } else if (type instanceof GraphQLObjectType || isNonNullOfType(type, GraphQLObjectType)) {
76
+ if (fieldEntry.extensions && fieldEntry.extensions.relation) {
77
+ if (!fieldEntry.extensions.relation.embedded) {
78
+ const key = fieldEntry.extensions.relation.connectionField || fieldEntry.name;
79
+ schemaArg[key] = mongoose.Schema.Types.ObjectId;
80
+ } else {
81
+ const entryType = unwrapNonNull(type);
82
+ if (entryType === gqlType) {
83
+ throw new Error('A type cannot have a field of its same type and embedded');
84
+ }
85
+ schemaArg[fieldEntryName] = generateSchemaDefinition(entryType);
86
+ }
87
+ }
88
+ } else if (getListItemType(type)) {
89
+ const itemType = getListItemType(type);
90
+ if (fieldEntry.extensions && fieldEntry.extensions.relation) {
91
+ if (fieldEntry.extensions.relation.embedded) {
92
+ if (itemType === gqlType) {
93
+ throw new Error('A type cannot have a field of its same type and embedded');
94
+ }
95
+ schemaArg[fieldEntryName] = [generateSchemaDefinition(itemType)];
96
+ }
97
+ } else if (listItemMatchesScalar(type, GraphQLID)) {
98
+ schemaArg[fieldEntryName] = [mongoose.Schema.Types.ObjectId];
99
+ } else if (listItemMatchesScalar(type, GraphQLString) || itemType instanceof GraphQLEnumType) {
100
+ schemaArg[fieldEntryName] = [String];
101
+ } else if (listItemMatchesScalar(type, GraphQLBoolean)) {
102
+ schemaArg[fieldEntryName] = [Boolean];
103
+ } else if (listItemMatchesScalar(type, GraphQLInt) || listItemMatchesScalar(type, GraphQLFloat)) {
104
+ schemaArg[fieldEntryName] = [Number];
105
+ } else if (isGraphQLisoDate(getEffectiveTypeName(itemType))) {
106
+ schemaArg[fieldEntryName] = [Date];
107
+ }
108
+ } else if (isGraphQLisoDate(getEffectiveTypeName(unwrapNonNull(type)))) {
109
+ schemaArg[fieldEntryName] = Date;
110
+ }
111
+ }
112
+
113
+ return schemaArg;
114
+ };
115
+
116
+ const findObjectIdFields = (schemaDefinition, parentPath = '') => {
117
+ const objectIdFields = [];
118
+
119
+ for (const [fieldName, fieldDefinition] of Object.entries(schemaDefinition)) {
120
+ const currentPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
121
+
122
+ if (fieldDefinition === mongoose.Schema.Types.ObjectId) {
123
+ objectIdFields.push(currentPath);
124
+ } else if (typeof fieldDefinition === 'object' && fieldDefinition !== null) {
125
+ if (Array.isArray(fieldDefinition)) {
126
+ const arrayElement = fieldDefinition[0];
127
+ if (typeof arrayElement === 'object' && arrayElement !== null) {
128
+ objectIdFields.push(...findObjectIdFields(arrayElement, currentPath));
129
+ }
130
+ } else if (fieldDefinition.type === mongoose.Schema.Types.ObjectId) {
131
+ objectIdFields.push(currentPath);
132
+ } else if (!fieldDefinition.type) {
133
+ objectIdFields.push(...findObjectIdFields(fieldDefinition, currentPath));
134
+ }
135
+ }
136
+ }
137
+
138
+ return objectIdFields;
139
+ };
140
+
141
+ const createSchemaWithIndexes = (schemaDefinition) => {
142
+ const schema = new mongoose.Schema(schemaDefinition);
143
+ findObjectIdFields(schemaDefinition).forEach((fieldPath) => {
144
+ schema.index({ [fieldPath]: 1 });
145
+ });
146
+ return schema;
147
+ };
148
+
149
+ export const createMongoModel = (gqlType, onModelCreated, { createCollection = true } = {}) => {
150
+ const schemaDefinition = generateSchemaDefinition(gqlType);
151
+ const schema = createSchemaWithIndexes(schemaDefinition);
152
+ const model = mongoose.model(gqlType.name, schema, gqlType.name);
153
+ if (onModelCreated) {
154
+ onModelCreated(model);
155
+ }
156
+ if (createCollection) {
157
+ model.createCollection();
158
+ }
159
+ return model;
160
+ };