@simtlix/simfinity-js 2.1.0 → 2.3.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 +627 -14
- package/package.json +1 -1
- package/src/index.js +102 -35
- package/src/scalars.js +188 -0
- package/src/validators.js +250 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -614,53 +614,53 @@ const executeRegisteredMutation = async (args, callback, session) => {
|
|
|
614
614
|
}
|
|
615
615
|
};
|
|
616
616
|
|
|
617
|
-
const iterateonCollectionFields = async (materializedModel, gqltype, objectId, session) => {
|
|
617
|
+
const iterateonCollectionFields = async (materializedModel, gqltype, objectId, session, context) => {
|
|
618
618
|
for (const [collectionFieldKey, collectionField] of
|
|
619
619
|
Object.entries(materializedModel.collectionFields)) {
|
|
620
620
|
if (collectionField.added) {
|
|
621
621
|
|
|
622
622
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
623
|
-
collectionField.added, operations.SAVE);
|
|
623
|
+
collectionField.added, operations.SAVE, context);
|
|
624
624
|
}
|
|
625
625
|
if (collectionField.updated) {
|
|
626
626
|
|
|
627
627
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
628
|
-
collectionField.updated, operations.UPDATE);
|
|
628
|
+
collectionField.updated, operations.UPDATE, context);
|
|
629
629
|
}
|
|
630
630
|
if (collectionField.deleted) {
|
|
631
631
|
|
|
632
632
|
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
633
|
-
collectionField.deleted, operations.DELETE);
|
|
633
|
+
collectionField.deleted, operations.DELETE, context);
|
|
634
634
|
}
|
|
635
635
|
}
|
|
636
636
|
};
|
|
637
637
|
|
|
638
|
-
const onDeleteObject = async (Model, gqltype, controller, args, session) => {
|
|
638
|
+
const onDeleteObject = async (Model, gqltype, controller, args, session, context) => {
|
|
639
639
|
const deletedObject = await Model.findById({ _id: args }).session(session).lean();
|
|
640
640
|
|
|
641
641
|
if (controller && controller.onDelete) {
|
|
642
|
-
await controller.onDelete(deletedObject, session);
|
|
642
|
+
await controller.onDelete(deletedObject, session, context);
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
return Model.findByIdAndDelete({ _id: args }).session(session);
|
|
646
646
|
};
|
|
647
647
|
|
|
648
|
-
const onDeleteSubject = async (Model, controller, id, session) => {
|
|
648
|
+
const onDeleteSubject = async (Model, controller, id, session, context) => {
|
|
649
649
|
const currentObject = await Model.findById({ _id: id }).session(session).lean();
|
|
650
650
|
|
|
651
651
|
if (controller && controller.onDelete) {
|
|
652
|
-
await controller.onDelete(currentObject, session);
|
|
652
|
+
await controller.onDelete(currentObject, session, context);
|
|
653
653
|
}
|
|
654
654
|
|
|
655
655
|
return Model.findByIdAndDelete({ _id: id }).session(session);
|
|
656
656
|
};
|
|
657
657
|
|
|
658
|
-
const onUpdateSubject = async (Model, gqltype, controller, args, session, linkToParent) => {
|
|
658
|
+
const onUpdateSubject = async (Model, gqltype, controller, args, session, linkToParent, context) => {
|
|
659
659
|
const materializedModel = await materializeModel(args, gqltype, linkToParent, 'UPDATE', session);
|
|
660
660
|
const objectId = args.id;
|
|
661
661
|
|
|
662
662
|
if (materializedModel.collectionFields) {
|
|
663
|
-
await iterateonCollectionFields(materializedModel, gqltype, objectId, session);
|
|
663
|
+
await iterateonCollectionFields(materializedModel, gqltype, objectId, session, context);
|
|
664
664
|
}
|
|
665
665
|
|
|
666
666
|
const currentObject = await Model.findById({ _id: objectId }).lean();
|
|
@@ -688,7 +688,7 @@ const onUpdateSubject = async (Model, gqltype, controller, args, session, linkTo
|
|
|
688
688
|
});
|
|
689
689
|
|
|
690
690
|
if (controller && controller.onUpdating) {
|
|
691
|
-
await controller.onUpdating(objectId, materializedModel.modelArgs, session);
|
|
691
|
+
await controller.onUpdating(objectId, materializedModel.modelArgs, session, context);
|
|
692
692
|
}
|
|
693
693
|
|
|
694
694
|
const result = Model.findByIdAndUpdate(
|
|
@@ -696,13 +696,13 @@ const onUpdateSubject = async (Model, gqltype, controller, args, session, linkTo
|
|
|
696
696
|
).session(session);
|
|
697
697
|
|
|
698
698
|
if (controller && controller.onUpdated) {
|
|
699
|
-
await controller.onUpdated(result, session);
|
|
699
|
+
await controller.onUpdated(result, session, context);
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
return result;
|
|
703
703
|
};
|
|
704
704
|
|
|
705
|
-
const onStateChanged = async (Model, gqltype, controller, args, session, actionField) => {
|
|
705
|
+
const onStateChanged = async (Model, gqltype, controller, args, session, actionField, context) => {
|
|
706
706
|
const storedModel = await Model.findById(args.id);
|
|
707
707
|
if (!storedModel) {
|
|
708
708
|
throw new SimfinityError(`${gqltype.name} ${args.id} is not valid`, 'NOT_VALID_ID', 404);
|
|
@@ -713,7 +713,7 @@ const onStateChanged = async (Model, gqltype, controller, args, session, actionF
|
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
args.state = actionField.to.name;
|
|
716
|
-
let result = await onUpdateSubject(Model, gqltype, controller, args, session);
|
|
716
|
+
let result = await onUpdateSubject(Model, gqltype, controller, args, session, null, context);
|
|
717
717
|
result = result.toObject();
|
|
718
718
|
result.state = actionField.to.value;
|
|
719
719
|
return result;
|
|
@@ -721,7 +721,7 @@ const onStateChanged = async (Model, gqltype, controller, args, session, actionF
|
|
|
721
721
|
throw new SimfinityError(`Action is not allowed from state ${storedModel.state}`, 'BAD_REQUEST', 400);
|
|
722
722
|
};
|
|
723
723
|
|
|
724
|
-
const onSaveObject = async (Model, gqltype, controller, args, session, linkToParent) => {
|
|
724
|
+
const onSaveObject = async (Model, gqltype, controller, args, session, linkToParent, context) => {
|
|
725
725
|
const materializedModel = await materializeModel(args, gqltype, linkToParent, 'CREATE', session);
|
|
726
726
|
if (typesDict.types[gqltype.name].stateMachine) {
|
|
727
727
|
materializedModel.modelArgs.state = typesDict.types[gqltype.name]
|
|
@@ -732,17 +732,17 @@ const onSaveObject = async (Model, gqltype, controller, args, session, linkToPar
|
|
|
732
732
|
newObject.$session(session);
|
|
733
733
|
|
|
734
734
|
if (controller && controller.onSaving) {
|
|
735
|
-
await controller.onSaving(newObject, args, session);
|
|
735
|
+
await controller.onSaving(newObject, args, session, context);
|
|
736
736
|
}
|
|
737
737
|
|
|
738
738
|
if (materializedModel.collectionFields) {
|
|
739
|
-
await iterateonCollectionFields(materializedModel, gqltype, newObject._id, session);
|
|
739
|
+
await iterateonCollectionFields(materializedModel, gqltype, newObject._id, session, context);
|
|
740
740
|
}
|
|
741
741
|
|
|
742
742
|
let result = await newObject.save();
|
|
743
743
|
result = result.toObject();
|
|
744
744
|
if (controller && controller.onSaved) {
|
|
745
|
-
await controller.onSaved(result, args, session);
|
|
745
|
+
await controller.onSaved(result, args, session, context);
|
|
746
746
|
}
|
|
747
747
|
if (typesDict.types[gqltype.name].stateMachine) {
|
|
748
748
|
result.state = typesDict.types[gqltype.name].stateMachine.initialState.value;
|
|
@@ -750,29 +750,29 @@ const onSaveObject = async (Model, gqltype, controller, args, session, linkToPar
|
|
|
750
750
|
return result;
|
|
751
751
|
};
|
|
752
752
|
|
|
753
|
-
export const saveObject = async (typeName, args, session) => {
|
|
753
|
+
export const saveObject = async (typeName, args, session, context) => {
|
|
754
754
|
const type = typesDict.types[typeName];
|
|
755
|
-
return onSaveObject(type.model, type.gqltype, type.controller, args, session);
|
|
755
|
+
return onSaveObject(type.model, type.gqltype, type.controller, args, session, null, context);
|
|
756
756
|
};
|
|
757
757
|
|
|
758
758
|
const executeOperation = async (Model, gqltype, controller,
|
|
759
|
-
args, operation, actionField, session) => {
|
|
759
|
+
args, operation, actionField, session, context) => {
|
|
760
760
|
const mySession = session || await mongoose.startSession();
|
|
761
761
|
await mySession.startTransaction();
|
|
762
762
|
try {
|
|
763
763
|
let newObject = null;
|
|
764
764
|
switch (operation) {
|
|
765
765
|
case operations.SAVE:
|
|
766
|
-
newObject = await onSaveObject(Model, gqltype, controller, args, mySession);
|
|
766
|
+
newObject = await onSaveObject(Model, gqltype, controller, args, mySession, null, context);
|
|
767
767
|
break;
|
|
768
768
|
case operations.UPDATE:
|
|
769
|
-
newObject = await onUpdateSubject(Model, gqltype, controller, args, mySession);
|
|
769
|
+
newObject = await onUpdateSubject(Model, gqltype, controller, args, mySession, null, context);
|
|
770
770
|
break;
|
|
771
771
|
case operations.DELETE:
|
|
772
|
-
newObject = await onDeleteObject(Model, gqltype, controller, args, mySession);
|
|
772
|
+
newObject = await onDeleteObject(Model, gqltype, controller, args, mySession, context);
|
|
773
773
|
break;
|
|
774
774
|
case operations.STATE_CHANGED:
|
|
775
|
-
newObject = await onStateChanged(Model, gqltype, controller, args, mySession, actionField);
|
|
775
|
+
newObject = await onStateChanged(Model, gqltype, controller, args, mySession, actionField, context);
|
|
776
776
|
break;
|
|
777
777
|
}
|
|
778
778
|
await mySession.commitTransaction();
|
|
@@ -781,7 +781,7 @@ const executeOperation = async (Model, gqltype, controller,
|
|
|
781
781
|
} catch (error) {
|
|
782
782
|
await mySession.abortTransaction();
|
|
783
783
|
if (error.errorLabels && error.errorLabels.includes('TransientTransactionError')) {
|
|
784
|
-
return executeOperation(Model, gqltype, controller, args, operation, actionField, mySession);
|
|
784
|
+
return executeOperation(Model, gqltype, controller, args, operation, actionField, mySession, context);
|
|
785
785
|
}
|
|
786
786
|
mySession.endSession();
|
|
787
787
|
throw error;
|
|
@@ -789,7 +789,7 @@ const executeOperation = async (Model, gqltype, controller,
|
|
|
789
789
|
};
|
|
790
790
|
|
|
791
791
|
const executeItemFunction = async (gqltype, collectionField, objectId, session,
|
|
792
|
-
collectionFieldsList, operationType) => {
|
|
792
|
+
collectionFieldsList, operationType, context) => {
|
|
793
793
|
const argTypes = gqltype.getFields();
|
|
794
794
|
const collectionGQLType = argTypes[collectionField].type.ofType;
|
|
795
795
|
const { connectionField } = argTypes[collectionField].extensions.relation;
|
|
@@ -802,7 +802,7 @@ const executeItemFunction = async (gqltype, collectionField, objectId, session,
|
|
|
802
802
|
await onSaveObject(typesDict.types[collectionGQLType.name].model, collectionGQLType,
|
|
803
803
|
typesDict.types[collectionGQLType.name].controller, collectionItem, session, (item) => {
|
|
804
804
|
item[connectionField] = objectId;
|
|
805
|
-
});
|
|
805
|
+
}, context);
|
|
806
806
|
};
|
|
807
807
|
break;
|
|
808
808
|
case operations.UPDATE:
|
|
@@ -810,13 +810,13 @@ const executeItemFunction = async (gqltype, collectionField, objectId, session,
|
|
|
810
810
|
await onUpdateSubject(typesDict.types[collectionGQLType.name].model, collectionGQLType,
|
|
811
811
|
typesDict.types[collectionGQLType.name].controller, collectionItem, session, (item) => {
|
|
812
812
|
item[connectionField] = objectId;
|
|
813
|
-
});
|
|
813
|
+
}, context);
|
|
814
814
|
};
|
|
815
815
|
break;
|
|
816
816
|
case operations.DELETE:
|
|
817
817
|
operationFunction = async (collectionItem) => {
|
|
818
818
|
await onDeleteSubject(typesDict.types[collectionGQLType.name].model,
|
|
819
|
-
typesDict.types[collectionGQLType.name].controller, collectionItem, session);
|
|
819
|
+
typesDict.types[collectionGQLType.name].controller, collectionItem, session, context);
|
|
820
820
|
};
|
|
821
821
|
}
|
|
822
822
|
|
|
@@ -846,6 +846,31 @@ const excecuteMiddleware = (context) => {
|
|
|
846
846
|
middleware();
|
|
847
847
|
};
|
|
848
848
|
|
|
849
|
+
const executeScope = async (params) => {
|
|
850
|
+
const { type, args, operation, context } = params;
|
|
851
|
+
|
|
852
|
+
if (!type || !type.gqltype || !type.gqltype.extensions) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const extensions = type.gqltype.extensions;
|
|
857
|
+
if (!extensions.scope || !extensions.scope[operation]) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const scopeFunction = extensions.scope[operation];
|
|
862
|
+
if (typeof scopeFunction !== 'function') {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Call the scope function with the same params as middleware
|
|
867
|
+
const result = await scopeFunction({ type, args, operation, context });
|
|
868
|
+
|
|
869
|
+
// For get_by_id, the scope function returns additional filters to merge
|
|
870
|
+
// For find and aggregate, it modifies args in place
|
|
871
|
+
return result;
|
|
872
|
+
};
|
|
873
|
+
|
|
849
874
|
const buildMutation = (name, includedMutationTypes, includedCustomMutations) => {
|
|
850
875
|
const rootQueryArgs = {};
|
|
851
876
|
rootQueryArgs.name = name;
|
|
@@ -872,7 +897,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
|
|
|
872
897
|
|
|
873
898
|
excecuteMiddleware(params);
|
|
874
899
|
return executeOperation(type.model, type.gqltype, type.controller,
|
|
875
|
-
args.input, operations.SAVE);
|
|
900
|
+
args.input, operations.SAVE, null, null, context);
|
|
876
901
|
},
|
|
877
902
|
};
|
|
878
903
|
rootQueryArgs.fields[`delete${type.simpleEntityEndpointName}`] = {
|
|
@@ -889,7 +914,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
|
|
|
889
914
|
|
|
890
915
|
excecuteMiddleware(params);
|
|
891
916
|
return executeOperation(type.model, type.gqltype, type.controller,
|
|
892
|
-
args.id, operations.DELETE);
|
|
917
|
+
args.id, operations.DELETE, null, null, context);
|
|
893
918
|
},
|
|
894
919
|
};
|
|
895
920
|
}
|
|
@@ -914,7 +939,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
|
|
|
914
939
|
|
|
915
940
|
excecuteMiddleware(params);
|
|
916
941
|
return executeOperation(type.model, type.gqltype, type.controller,
|
|
917
|
-
args.input, operations.UPDATE);
|
|
942
|
+
args.input, operations.UPDATE, null, null, context);
|
|
918
943
|
},
|
|
919
944
|
};
|
|
920
945
|
if (type.stateMachine) {
|
|
@@ -936,7 +961,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
|
|
|
936
961
|
|
|
937
962
|
excecuteMiddleware(params);
|
|
938
963
|
return executeOperation(type.model, type.gqltype, type.controller,
|
|
939
|
-
args.input, operations.STATE_CHANGED, actionField);
|
|
964
|
+
args.input, operations.STATE_CHANGED, actionField, null, context);
|
|
940
965
|
},
|
|
941
966
|
};
|
|
942
967
|
}
|
|
@@ -1829,7 +1854,44 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1829
1854
|
context,
|
|
1830
1855
|
};
|
|
1831
1856
|
excecuteMiddleware(params);
|
|
1832
|
-
|
|
1857
|
+
|
|
1858
|
+
// Check if scope is defined for get_by_id
|
|
1859
|
+
const hasScope = type.gqltype.extensions && type.gqltype.extensions.scope && type.gqltype.extensions.scope.get_by_id;
|
|
1860
|
+
|
|
1861
|
+
if (hasScope) {
|
|
1862
|
+
// Build query args with id filter - scope function will modify this
|
|
1863
|
+
const queryArgs = {
|
|
1864
|
+
id: { operator: 'EQ', value: args.id },
|
|
1865
|
+
};
|
|
1866
|
+
|
|
1867
|
+
// Create temporary params with queryArgs for scope function
|
|
1868
|
+
const scopeParams = {
|
|
1869
|
+
type,
|
|
1870
|
+
args: queryArgs,
|
|
1871
|
+
operation: 'get_by_id',
|
|
1872
|
+
context,
|
|
1873
|
+
};
|
|
1874
|
+
|
|
1875
|
+
// Execute scope which will modify queryArgs in place
|
|
1876
|
+
await executeScope(scopeParams);
|
|
1877
|
+
|
|
1878
|
+
// Build aggregation pipeline from the combined filters
|
|
1879
|
+
const aggregateClauses = await buildQuery(queryArgs, type.gqltype);
|
|
1880
|
+
|
|
1881
|
+
// Execute the query and get the first result
|
|
1882
|
+
let result;
|
|
1883
|
+
if (aggregateClauses.length === 0) {
|
|
1884
|
+
result = await type.model.findOne({ _id: args.id });
|
|
1885
|
+
} else {
|
|
1886
|
+
const results = await type.model.aggregate(aggregateClauses);
|
|
1887
|
+
result = results.length > 0 ? results[0] : null;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
return result;
|
|
1891
|
+
} else {
|
|
1892
|
+
// No scope defined, use the original findById
|
|
1893
|
+
return await type.model.findById(args.id);
|
|
1894
|
+
}
|
|
1833
1895
|
},
|
|
1834
1896
|
};
|
|
1835
1897
|
|
|
@@ -1848,6 +1910,7 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1848
1910
|
context,
|
|
1849
1911
|
};
|
|
1850
1912
|
excecuteMiddleware(params);
|
|
1913
|
+
await executeScope(params);
|
|
1851
1914
|
const aggregateClauses = await buildQuery(args, type.gqltype);
|
|
1852
1915
|
if (args.pagination && args.pagination.count) {
|
|
1853
1916
|
const aggregateClausesForCount = await buildQuery(args, type.gqltype, true);
|
|
@@ -1882,6 +1945,7 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1882
1945
|
context,
|
|
1883
1946
|
};
|
|
1884
1947
|
excecuteMiddleware(params);
|
|
1948
|
+
await executeScope(params);
|
|
1885
1949
|
const aggregateClauses = await buildAggregationQuery(args, type.gqltype, args.aggregation);
|
|
1886
1950
|
const result = await type.model.aggregate(aggregateClauses);
|
|
1887
1951
|
return result;
|
|
@@ -2075,6 +2139,9 @@ export const addNoEndpointType = (gqltype) => {
|
|
|
2075
2139
|
|
|
2076
2140
|
export { createValidatedScalar };
|
|
2077
2141
|
|
|
2142
|
+
export { default as validators } from './validators.js';
|
|
2143
|
+
export { default as scalars } from './scalars.js';
|
|
2144
|
+
|
|
2078
2145
|
const createArgsForQuery = (argTypes) => {
|
|
2079
2146
|
const argsObject = {};
|
|
2080
2147
|
|
package/src/scalars.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLString, GraphQLInt, GraphQLFloat,
|
|
3
|
+
} from 'graphql';
|
|
4
|
+
import { createValidatedScalar } from './index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Email scalar - validates email format
|
|
8
|
+
* Type name: Email_String
|
|
9
|
+
*/
|
|
10
|
+
export const EmailScalar = createValidatedScalar(
|
|
11
|
+
'Email',
|
|
12
|
+
'A valid email address',
|
|
13
|
+
GraphQLString,
|
|
14
|
+
(value) => {
|
|
15
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16
|
+
if (!emailRegex.test(value)) {
|
|
17
|
+
throw new Error('Invalid email format');
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* URL scalar - validates URL format
|
|
24
|
+
* Type name: URL_String
|
|
25
|
+
*/
|
|
26
|
+
export const URLScalar = createValidatedScalar(
|
|
27
|
+
'URL',
|
|
28
|
+
'A valid URL',
|
|
29
|
+
GraphQLString,
|
|
30
|
+
(value) => {
|
|
31
|
+
try {
|
|
32
|
+
new URL(value);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error('Invalid URL format');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PositiveInt scalar - validates positive integers
|
|
41
|
+
* Type name: PositiveInt_Int
|
|
42
|
+
*/
|
|
43
|
+
export const PositiveIntScalar = createValidatedScalar(
|
|
44
|
+
'PositiveInt',
|
|
45
|
+
'A positive integer',
|
|
46
|
+
GraphQLInt,
|
|
47
|
+
(value) => {
|
|
48
|
+
if (value <= 0) {
|
|
49
|
+
throw new Error('Value must be positive');
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* PositiveFloat scalar - validates positive floats
|
|
56
|
+
* Type name: PositiveFloat_Float
|
|
57
|
+
*/
|
|
58
|
+
export const PositiveFloatScalar = createValidatedScalar(
|
|
59
|
+
'PositiveFloat',
|
|
60
|
+
'A positive float',
|
|
61
|
+
GraphQLFloat,
|
|
62
|
+
(value) => {
|
|
63
|
+
if (value <= 0) {
|
|
64
|
+
throw new Error('Value must be positive');
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Factory function to create a bounded string scalar
|
|
71
|
+
* @param {string} name - Name for the scalar
|
|
72
|
+
* @param {number} min - Minimum length
|
|
73
|
+
* @param {number} max - Maximum length
|
|
74
|
+
* @returns {GraphQLScalarType} A scalar type with length validation
|
|
75
|
+
*/
|
|
76
|
+
export const createBoundedStringScalar = (name, min, max) => {
|
|
77
|
+
return createValidatedScalar(
|
|
78
|
+
name,
|
|
79
|
+
`A string with length between ${min} and ${max} characters`,
|
|
80
|
+
GraphQLString,
|
|
81
|
+
(value) => {
|
|
82
|
+
if (typeof value !== 'string') {
|
|
83
|
+
throw new Error('Value must be a string');
|
|
84
|
+
}
|
|
85
|
+
if (min !== undefined && value.length < min) {
|
|
86
|
+
throw new Error(`String must be at least ${min} characters`);
|
|
87
|
+
}
|
|
88
|
+
if (max !== undefined && value.length > max) {
|
|
89
|
+
throw new Error(`String must be at most ${max} characters`);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Factory function to create a bounded integer scalar
|
|
97
|
+
* @param {string} name - Name for the scalar
|
|
98
|
+
* @param {number} min - Minimum value
|
|
99
|
+
* @param {number} max - Maximum value
|
|
100
|
+
* @returns {GraphQLScalarType} A scalar type with range validation
|
|
101
|
+
*/
|
|
102
|
+
export const createBoundedIntScalar = (name, min, max) => {
|
|
103
|
+
return createValidatedScalar(
|
|
104
|
+
name,
|
|
105
|
+
`An integer between ${min} and ${max}`,
|
|
106
|
+
GraphQLInt,
|
|
107
|
+
(value) => {
|
|
108
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
109
|
+
throw new Error('Value must be a number');
|
|
110
|
+
}
|
|
111
|
+
if (min !== undefined && value < min) {
|
|
112
|
+
throw new Error(`Value must be at least ${min}`);
|
|
113
|
+
}
|
|
114
|
+
if (max !== undefined && value > max) {
|
|
115
|
+
throw new Error(`Value must be at most ${max}`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Factory function to create a bounded float scalar
|
|
123
|
+
* @param {string} name - Name for the scalar
|
|
124
|
+
* @param {number} min - Minimum value
|
|
125
|
+
* @param {number} max - Maximum value
|
|
126
|
+
* @returns {GraphQLScalarType} A scalar type with range validation
|
|
127
|
+
*/
|
|
128
|
+
export const createBoundedFloatScalar = (name, min, max) => {
|
|
129
|
+
return createValidatedScalar(
|
|
130
|
+
name,
|
|
131
|
+
`A float between ${min} and ${max}`,
|
|
132
|
+
GraphQLFloat,
|
|
133
|
+
(value) => {
|
|
134
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
135
|
+
throw new Error('Value must be a number');
|
|
136
|
+
}
|
|
137
|
+
if (min !== undefined && value < min) {
|
|
138
|
+
throw new Error(`Value must be at least ${min}`);
|
|
139
|
+
}
|
|
140
|
+
if (max !== undefined && value > max) {
|
|
141
|
+
throw new Error(`Value must be at most ${max}`);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Factory function to create a regex pattern string scalar
|
|
149
|
+
* @param {string} name - Name for the scalar
|
|
150
|
+
* @param {RegExp|string} pattern - Regex pattern to validate against
|
|
151
|
+
* @param {string} message - Error message if validation fails
|
|
152
|
+
* @returns {GraphQLScalarType} A scalar type with pattern validation
|
|
153
|
+
*/
|
|
154
|
+
export const createPatternStringScalar = (name, pattern, message) => {
|
|
155
|
+
const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
|
|
156
|
+
const errorMessage = message || 'Value does not match required pattern';
|
|
157
|
+
|
|
158
|
+
return createValidatedScalar(
|
|
159
|
+
name,
|
|
160
|
+
`A string matching the pattern: ${pattern}`,
|
|
161
|
+
GraphQLString,
|
|
162
|
+
(value) => {
|
|
163
|
+
if (typeof value !== 'string') {
|
|
164
|
+
throw new Error('Value must be a string');
|
|
165
|
+
}
|
|
166
|
+
if (!regex.test(value)) {
|
|
167
|
+
throw new Error(errorMessage);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Export all scalars as an object for convenience
|
|
174
|
+
const scalars = {
|
|
175
|
+
// Pre-built scalars
|
|
176
|
+
EmailScalar,
|
|
177
|
+
URLScalar,
|
|
178
|
+
PositiveIntScalar,
|
|
179
|
+
PositiveFloatScalar,
|
|
180
|
+
// Factory functions
|
|
181
|
+
createBoundedStringScalar,
|
|
182
|
+
createBoundedIntScalar,
|
|
183
|
+
createBoundedFloatScalar,
|
|
184
|
+
createPatternStringScalar,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default scalars;
|
|
188
|
+
|