@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/eslint.config.mjs
CHANGED
|
@@ -16,48 +16,56 @@ export default [
|
|
|
16
16
|
{
|
|
17
17
|
ignores: ["node_modules/*", "data/*", "eslint.config.mjs"],
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
js.configs.recommended,
|
|
20
20
|
{
|
|
21
21
|
files: ["**/*.js"],
|
|
22
22
|
languageOptions: {
|
|
23
23
|
globals: {
|
|
24
|
-
...globals.commonjs,
|
|
25
24
|
...globals.node,
|
|
26
|
-
...globals.
|
|
25
|
+
...globals.es2024,
|
|
27
26
|
},
|
|
28
|
-
|
|
29
27
|
ecmaVersion: 2024,
|
|
30
|
-
sourceType: "
|
|
28
|
+
sourceType: "module",
|
|
31
29
|
},
|
|
32
|
-
|
|
33
30
|
rules: {
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
31
|
+
// Code style and best practices (relaxed to match existing code)
|
|
32
|
+
"quotes": ["error", "single"],
|
|
33
|
+
"semi": ["error", "always"],
|
|
34
|
+
"comma-dangle": ["error", "always-multiline"],
|
|
35
|
+
"object-curly-spacing": ["error", "always"],
|
|
36
|
+
"array-bracket-spacing": ["error", "never"],
|
|
37
|
+
|
|
38
|
+
// ES6+ features
|
|
39
|
+
"prefer-const": "error",
|
|
40
|
+
"no-var": "error",
|
|
41
|
+
"prefer-arrow-callback": "off", // Allow function declarations
|
|
42
|
+
"arrow-spacing": "error",
|
|
43
|
+
|
|
44
|
+
// Best practices
|
|
45
|
+
"no-console": "off", // Allow console for this project
|
|
46
|
+
"no-underscore-dangle": "off", // Allow underscore dangle for MongoDB _id
|
|
38
47
|
"no-await-in-loop": "off",
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
"max-len": "off", // Disable max-len for now
|
|
49
|
+
"indent": "off", // Disable indent for now to match existing style
|
|
50
|
+
|
|
51
|
+
// Parameter reassignment (common in GraphQL resolvers)
|
|
52
|
+
"no-param-reassign": ["error", { "props": false }],
|
|
53
|
+
|
|
54
|
+
// Function formatting
|
|
44
55
|
"function-paren-newline": "off",
|
|
45
56
|
"function-call-argument-newline": "off",
|
|
46
|
-
|
|
57
|
+
|
|
58
|
+
// Restricted syntax
|
|
47
59
|
"no-restricted-syntax": ["error", {
|
|
48
60
|
selector: "ForInStatement",
|
|
49
61
|
message: "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
|
|
50
62
|
}, {
|
|
51
|
-
selector: "LabeledStatement",
|
|
63
|
+
selector: "LabeledStatement",
|
|
52
64
|
message: "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
|
|
53
65
|
}, {
|
|
54
66
|
selector: "WithStatement",
|
|
55
67
|
message: "`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
|
|
56
68
|
}],
|
|
57
|
-
|
|
58
|
-
"import/no-unresolved": ["error", {
|
|
59
|
-
ignore: ["graphql", "mongoose"],
|
|
60
|
-
}],
|
|
61
69
|
},
|
|
62
70
|
},
|
|
63
71
|
];
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simtlix/simfinity-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
-
"test:watch": "
|
|
9
|
-
"test:coverage": "
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"test:watch": "vitest",
|
|
10
|
+
"test:coverage": "vitest --coverage",
|
|
10
11
|
"lint": "eslint '**/*.js'",
|
|
11
12
|
"lint-fix": "eslint --fix '**/*.js'"
|
|
12
13
|
},
|
|
@@ -24,34 +25,16 @@
|
|
|
24
25
|
"mongoose": "^8.16.2"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@eslint/compat": "^1.2.0",
|
|
28
|
-
"@eslint/eslintrc": "^3.1.0",
|
|
29
28
|
"@eslint/js": "^9.30.1",
|
|
30
29
|
"eslint": "^9.30.1",
|
|
31
|
-
"eslint-config-airbnb-base": "^15.0.0",
|
|
32
|
-
"eslint-plugin-import": "^2.32.0",
|
|
33
|
-
"ghooks": "^2.0.4",
|
|
34
30
|
"globals": "^16.3.0",
|
|
35
|
-
"
|
|
31
|
+
"vitest": "^3.2.4"
|
|
36
32
|
},
|
|
37
33
|
"config": {
|
|
38
|
-
"ghooks": {
|
|
39
|
-
"pre-commit": "npm run lint || npm run lint-fix"
|
|
40
|
-
},
|
|
41
34
|
"owner": "simtlix"
|
|
42
35
|
},
|
|
43
36
|
"optionalDependencies": {
|
|
44
37
|
"graphql": "^16.11.0",
|
|
45
38
|
"mongoose": "^8.16.2"
|
|
46
|
-
},
|
|
47
|
-
"jest": {
|
|
48
|
-
"testEnvironment": "node",
|
|
49
|
-
"testMatch": [
|
|
50
|
-
"**/tests/**/*.test.js"
|
|
51
|
-
],
|
|
52
|
-
"collectCoverageFrom": [
|
|
53
|
-
"src/**/*.js",
|
|
54
|
-
"!src/index.js"
|
|
55
|
-
]
|
|
56
39
|
}
|
|
57
40
|
}
|
package/src/const/QLOperator.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { GraphQLEnumType } = graphql;
|
|
1
|
+
import { GraphQLEnumType } from 'graphql';
|
|
4
2
|
|
|
5
3
|
const QLOperator = new GraphQLEnumType({
|
|
6
4
|
name: 'QLOperator',
|
|
@@ -38,4 +36,4 @@ const QLOperator = new GraphQLEnumType({
|
|
|
38
36
|
},
|
|
39
37
|
});
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
export default QLOperator;
|
package/src/const/QLSort.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const {
|
|
1
|
+
import {
|
|
4
2
|
GraphQLInputObjectType,
|
|
5
3
|
GraphQLNonNull,
|
|
6
4
|
GraphQLEnumType,
|
|
7
5
|
GraphQLString,
|
|
8
|
-
}
|
|
6
|
+
} from 'graphql';
|
|
9
7
|
|
|
10
8
|
const QLSortOrder = new GraphQLEnumType({
|
|
11
9
|
name: 'QLSortOrder',
|
|
@@ -27,4 +25,4 @@ const QLSort = new GraphQLInputObjectType({
|
|
|
27
25
|
}),
|
|
28
26
|
});
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
export default QLSort;
|
package/src/const/QLValue.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { GraphQLScalarType, Kind } = graphql;
|
|
1
|
+
import { GraphQLScalarType, Kind } from 'graphql';
|
|
4
2
|
|
|
5
3
|
function parseQLValue(value) {
|
|
6
4
|
return value;
|
|
@@ -38,4 +36,4 @@ const QLValue = new GraphQLScalarType({
|
|
|
38
36
|
},
|
|
39
37
|
});
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
export default QLValue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import SimfinityError from './simfinity.error.js';
|
|
2
2
|
|
|
3
3
|
class InternalServerError extends SimfinityError {
|
|
4
4
|
constructor(message, cause) {
|
|
@@ -8,4 +8,4 @@ class InternalServerError extends SimfinityError {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
export default InternalServerError;
|
package/src/index.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
const mongoose = require('mongoose');
|
|
3
|
-
|
|
4
|
-
const SimfinityError = require('./errors/simfinity.error');
|
|
5
|
-
const InternalServerError = require('./errors/internal-server.error');
|
|
6
|
-
const QLOperator = require('./const/QLOperator');
|
|
7
|
-
const QLValue = require('./const/QLValue');
|
|
8
|
-
const QLSort = require('./const/QLSort');
|
|
9
|
-
|
|
10
|
-
mongoose.set('strictQuery', false);
|
|
11
|
-
|
|
12
|
-
const {
|
|
1
|
+
import {
|
|
13
2
|
GraphQLObjectType, GraphQLString, GraphQLID, GraphQLSchema, GraphQLList,
|
|
14
3
|
GraphQLNonNull, GraphQLInputObjectType, GraphQLScalarType, __Field,
|
|
15
4
|
GraphQLInt, GraphQLEnumType, GraphQLBoolean, GraphQLFloat, Kind,
|
|
16
|
-
}
|
|
5
|
+
} from 'graphql';
|
|
6
|
+
import mongoose from 'mongoose';
|
|
7
|
+
|
|
8
|
+
import SimfinityError from './errors/simfinity.error.js';
|
|
9
|
+
import InternalServerError from './errors/internal-server.error.js';
|
|
10
|
+
import QLOperator from './const/QLOperator.js';
|
|
11
|
+
import QLValue from './const/QLValue.js';
|
|
12
|
+
import QLSort from './const/QLSort.js';
|
|
13
|
+
|
|
14
|
+
mongoose.set('strictQuery', false);
|
|
17
15
|
|
|
18
16
|
// Adding 'extensions' field into instronspection query
|
|
19
17
|
const RelationType = new GraphQLObjectType({
|
|
@@ -83,19 +81,19 @@ const buildErrorFormatter = (callback) => {
|
|
|
83
81
|
|
|
84
82
|
const middlewares = [];
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
export const use = (middleware) => {
|
|
87
85
|
middlewares.push(middleware);
|
|
88
86
|
};
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
export { buildErrorFormatter };
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
export { SimfinityError };
|
|
93
91
|
|
|
94
|
-
|
|
92
|
+
export { InternalServerError };
|
|
95
93
|
|
|
96
94
|
let preventCollectionCreation = false;
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
export const preventCreatingCollection = (prevent) => {
|
|
99
97
|
preventCollectionCreation = !!prevent;
|
|
100
98
|
};
|
|
101
99
|
|
|
@@ -416,7 +414,7 @@ const buildInputType = (gqltype) => {
|
|
|
416
414
|
|
|
417
415
|
const getInputType = (type) => typesDict.types[type.name].inputType;
|
|
418
416
|
|
|
419
|
-
|
|
417
|
+
export { getInputType };
|
|
420
418
|
|
|
421
419
|
const buildPendingInputTypes = (waitingForInputType) => {
|
|
422
420
|
const stillWaitingInputType = {};
|
|
@@ -549,17 +547,17 @@ const iterateonCollectionFields = async (materializedModel, gqltype, objectId, s
|
|
|
549
547
|
for (const [collectionFieldKey, collectionField] of
|
|
550
548
|
Object.entries(materializedModel.collectionFields)) {
|
|
551
549
|
if (collectionField.added) {
|
|
552
|
-
|
|
550
|
+
|
|
553
551
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
554
552
|
collectionField.added, operations.SAVE);
|
|
555
553
|
}
|
|
556
554
|
if (collectionField.updated) {
|
|
557
|
-
|
|
555
|
+
|
|
558
556
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
559
557
|
collectionField.updated, operations.UPDATE);
|
|
560
558
|
}
|
|
561
559
|
if (collectionField.deleted) {
|
|
562
|
-
|
|
560
|
+
|
|
563
561
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
564
562
|
collectionField.deleted, operations.DELETE);
|
|
565
563
|
}
|
|
@@ -681,7 +679,7 @@ const onSaveObject = async (Model, gqltype, controller, args, session, linkToPar
|
|
|
681
679
|
return result;
|
|
682
680
|
};
|
|
683
681
|
|
|
684
|
-
|
|
682
|
+
export const saveObject = async (typeName, args, session) => {
|
|
685
683
|
const type = typesDict.types[typeName];
|
|
686
684
|
return onSaveObject(type.model, type.gqltype, type.controller, args, session);
|
|
687
685
|
};
|
|
@@ -1389,9 +1387,9 @@ const buildQuery = async (input, gqltype, isCount) => {
|
|
|
1389
1387
|
|
|
1390
1388
|
if (sort.field.indexOf('.') >= 0) {
|
|
1391
1389
|
const sortParts = sort.field.split('.');
|
|
1392
|
-
|
|
1390
|
+
|
|
1393
1391
|
fixedSortField = sortParts[0];
|
|
1394
|
-
|
|
1392
|
+
|
|
1395
1393
|
for (let i = 1; i < sortParts.length - 1; i++) {
|
|
1396
1394
|
fixedSortField += `_${sortParts[i]}`;
|
|
1397
1395
|
}
|
|
@@ -1536,15 +1534,15 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1536
1534
|
|
|
1537
1535
|
/* Creating a new GraphQL Schema, with options query which defines query
|
|
1538
1536
|
we will allow users to use when they are making request. */
|
|
1539
|
-
|
|
1537
|
+
export const createSchema = (includedQueryTypes,
|
|
1540
1538
|
includedMutationTypes, includedCustomMutations) => new GraphQLSchema({
|
|
1541
1539
|
query: buildRootQuery('RootQueryType', includedQueryTypes),
|
|
1542
1540
|
mutation: buildMutation('Mutation', includedMutationTypes, includedCustomMutations),
|
|
1543
1541
|
});
|
|
1544
1542
|
|
|
1545
|
-
|
|
1543
|
+
export const getModel = (gqltype) => typesDict.types[gqltype.name].model;
|
|
1546
1544
|
|
|
1547
|
-
|
|
1545
|
+
export const registerMutation = (name, description, inputModel, outputModel, callback) => {
|
|
1548
1546
|
registeredMutations[name] = {
|
|
1549
1547
|
description,
|
|
1550
1548
|
inputModel,
|
|
@@ -1563,43 +1561,46 @@ const autoGenerateResolvers = (gqltype) => {
|
|
|
1563
1561
|
if (fieldEntry.extensions && fieldEntry.extensions.relation) {
|
|
1564
1562
|
const { relation } = fieldEntry.extensions;
|
|
1565
1563
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1564
|
+
// Only generate resolvers for non-embedded relationships
|
|
1565
|
+
if (!relation.embedded) {
|
|
1566
|
+
if (fieldEntry.type instanceof GraphQLList) {
|
|
1567
|
+
// Collection field - generate resolve for one-to-many relationship
|
|
1568
|
+
const relatedType = fieldEntry.type.ofType;
|
|
1569
|
+
const connectionField = relation.connectionField || fieldName;
|
|
1570
|
+
|
|
1571
|
+
fieldEntry.resolve = (parent) => {
|
|
1572
|
+
// Lazy lookup of the related model
|
|
1573
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1574
|
+
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1575
|
+
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1576
|
+
}
|
|
1577
|
+
const query = {};
|
|
1578
|
+
query[connectionField] = parent.id || parent._id;
|
|
1579
|
+
return relatedTypeInfo.model.find(query);
|
|
1580
|
+
};
|
|
1581
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1582
|
+
|| (fieldEntry.type instanceof GraphQLNonNull && fieldEntry.type.ofType instanceof GraphQLObjectType)) {
|
|
1583
|
+
// Single object field - generate resolve for one-to-one relationship
|
|
1584
|
+
const relatedType = fieldEntry.type instanceof GraphQLNonNull ? fieldEntry.type.ofType : fieldEntry.type;
|
|
1585
|
+
const connectionField = relation.connectionField || fieldName;
|
|
1586
|
+
|
|
1587
|
+
fieldEntry.resolve = (parent) => {
|
|
1588
|
+
// Lazy lookup of the related model
|
|
1589
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1590
|
+
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1591
|
+
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1592
|
+
}
|
|
1593
|
+
const relatedId = parent[connectionField] || parent[fieldName];
|
|
1594
|
+
return relatedId ? relatedTypeInfo.model.findById(relatedId) : null;
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1596
1597
|
}
|
|
1597
1598
|
}
|
|
1598
1599
|
}
|
|
1599
1600
|
}
|
|
1600
1601
|
};
|
|
1601
1602
|
|
|
1602
|
-
|
|
1603
|
+
export const connect = (model, gqltype, simpleEntityEndpointName,
|
|
1603
1604
|
listEntitiesEndpointName, controller, onModelCreated, stateMachine) => {
|
|
1604
1605
|
waitingInputType[gqltype.name] = {
|
|
1605
1606
|
model,
|
|
@@ -1621,7 +1622,7 @@ module.exports.connect = (model, gqltype, simpleEntityEndpointName,
|
|
|
1621
1622
|
autoGenerateResolvers(gqltype);
|
|
1622
1623
|
};
|
|
1623
1624
|
|
|
1624
|
-
|
|
1625
|
+
export const addNoEndpointType = (gqltype) => {
|
|
1625
1626
|
waitingInputType[gqltype.name] = {
|
|
1626
1627
|
gqltype,
|
|
1627
1628
|
};
|
|
@@ -1652,4 +1653,4 @@ module.exports.addNoEndpointType = (gqltype) => {
|
|
|
1652
1653
|
autoGenerateResolvers(gqltype);
|
|
1653
1654
|
};
|
|
1654
1655
|
|
|
1655
|
-
|
|
1656
|
+
export { createValidatedScalar };
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
describe, test, expect, beforeEach, afterEach, vi,
|
|
3
|
+
} from 'vitest';
|
|
4
|
+
import mongoose from 'mongoose';
|
|
5
|
+
import { GraphQLObjectType, GraphQLString, GraphQLID } from 'graphql';
|
|
6
|
+
import * as simfinity from '../src/index.js';
|
|
4
7
|
|
|
5
8
|
describe('preventCreatingCollection option', () => {
|
|
6
9
|
let createCollectionSpy;
|
|
7
10
|
|
|
8
11
|
beforeEach(() => {
|
|
9
|
-
// Reset modules to have a clean state for each test
|
|
10
|
-
jest.resetModules();
|
|
11
12
|
// Spy on the createCollection method of the mongoose model prototype
|
|
12
|
-
createCollectionSpy =
|
|
13
|
+
createCollectionSpy = vi.spyOn(mongoose.Model, 'createCollection').mockImplementation(() => Promise.resolve());
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
afterEach(() => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
describe, test, expect, beforeAll,
|
|
3
|
+
} from 'vitest';
|
|
4
|
+
import {
|
|
2
5
|
GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList, GraphQLNonNull,
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
} from 'graphql';
|
|
7
|
+
import { createValidatedScalar } from '../src/index.js';
|
|
8
|
+
import * as simfinity from '../src/index.js';
|
|
6
9
|
|
|
7
10
|
describe('Custom Validated Scalar Types', () => {
|
|
8
11
|
let EmailScalar;
|