@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/README.md +434 -72
- package/package.json +20 -5
- package/src/auth/errors.js +2 -44
- package/src/auth/expressions.js +2 -273
- package/src/auth/index.js +3 -390
- package/src/auth/register-mongo-object-id.js +7 -0
- package/src/auth/rules.js +3 -273
- package/src/const/QLOperator.js +1 -39
- package/src/const/QLSort.js +1 -28
- package/src/const/QLValue.js +1 -39
- package/src/errors/internal-server.error.js +1 -11
- package/src/errors/simfinity.error.js +1 -15
- package/src/index.js +43 -1990
- package/src/mcp.js +2 -0
- package/src/mongo/adapter.js +111 -0
- package/src/mongo/models.js +160 -0
- package/src/mongo/queries.js +570 -0
- package/src/mongo/transactions.js +81 -0
- package/src/plugins.js +2 -53
- package/src/scalars.js +2 -188
- package/src/validators.js +2 -250
- package/types/index.d.ts +198 -0
package/src/mcp.js
ADDED
|
@@ -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
|
+
};
|