@steedos/service-api 2.5.5 → 2.5.6-beta.4

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.
Files changed (2) hide show
  1. package/index.js +172 -4
  2. package/package.json +7 -6
package/index.js CHANGED
@@ -13,9 +13,11 @@ const {
13
13
  const SteedosRouter = require('@steedos/router');
14
14
  const _ = require('lodash');
15
15
  const ServiceObjectGraphql = require('@steedos/service-object-graphql')
16
+ const open = require('open');
17
+ const DataLoader = require("dataloader");
18
+ const { LRUMap } = require('lru_map');
16
19
  const validator = require('validator');
17
- const enablePlayground = validator.toBoolean(process.env.STEEDOS_GRAPHQL_ENABLE_CONSOLE || 'true', true)
18
-
20
+ const enablePlayground = validator.toBoolean(process.env.STEEDOS_GRAPHQL_ENABLE_CONSOLE || 'true', true);
19
21
  const mixinOptions = {
20
22
 
21
23
  // Global GraphQL typeDefs
@@ -313,6 +315,76 @@ module.exports = {
313
315
  },
314
316
 
315
317
  methods: {
318
+ /**
319
+ * Get the unique key assigned to the DataLoader map
320
+ * @param {string} actionName - Fully qualified action name to bind to dataloader
321
+ * @param {Object.<string, any>} staticParams - Static parameters to use in dataloader
322
+ * @param {Object.<string, any>} args - Arguments passed to GraphQL child resolver
323
+ * @returns {string} Key to the dataloader instance
324
+ */
325
+ getObjectDataLoaderMapKey(objectName) {
326
+ if (objectName) {
327
+ return `object:${objectName}`;
328
+ }
329
+ // 如果没有objectName,则抛出错误信息
330
+ throw new Error("objectName is required");
331
+ },
332
+
333
+
334
+ async objectDataLoaderHandler(actionName, staticParams, rootParams, graphqlCtx) {
335
+ const rootKeys = Object.keys(rootParams);
336
+ const {root, args, context, resolveInfo} = graphqlCtx;
337
+ const dataLoaderMapKey = this.getObjectDataLoaderMapKey(
338
+ staticParams.__objectName || staticParams.objectName
339
+ );
340
+ const objectDataLoaders = this.objectDataLoaders;
341
+ // if a dataLoader batching parameter is specified, then all root params can be data loaded;
342
+ // otherwise use only the primary rootParam
343
+ const primaryDataLoaderRootKey = rootKeys[0]; // for dataloader, use the first root key only
344
+ const dataLoaderBatchParam = this.dataLoaderBatchParams.get(actionName);
345
+ const dataLoaderUseAllRootKeys = dataLoaderBatchParam != null;
346
+
347
+ // check to see if the DataLoader has already been added to the GraphQL context; if not then add it for subsequent use
348
+ let dataLoader;
349
+ // console.log(`objectDataLoaders.has(dataLoaderMapKey)`, objectDataLoaders.has(dataLoaderMapKey))
350
+ if (objectDataLoaders.has(dataLoaderMapKey)) {
351
+ dataLoader = objectDataLoaders.get(dataLoaderMapKey);
352
+ } else {
353
+ const batchedParamKey =
354
+ dataLoaderBatchParam || rootParams[primaryDataLoaderRootKey];
355
+ dataLoader = this.buildDataLoader(
356
+ context.ctx,
357
+ actionName,
358
+ batchedParamKey,
359
+ staticParams,
360
+ args,
361
+ { hashCacheKey: dataLoaderUseAllRootKeys } // must hash the cache key if not loading scalar
362
+ );
363
+ objectDataLoaders.set(dataLoaderMapKey, dataLoader);
364
+ }
365
+
366
+ let dataLoaderKey;
367
+ if (dataLoaderUseAllRootKeys) {
368
+ if (root && rootKeys) {
369
+ dataLoaderKey = {};
370
+
371
+ rootKeys.forEach(key => {
372
+ _.set(dataLoaderKey, rootParams[key], _.get(root, key));
373
+ });
374
+ }
375
+ } else {
376
+ dataLoaderKey = root && _.get(root, primaryDataLoaderRootKey);
377
+ }
378
+
379
+ if (dataLoaderKey == null) {
380
+ return null;
381
+ }
382
+
383
+ return Array.isArray(dataLoaderKey)
384
+ ? await dataLoader.loadMany(dataLoaderKey)
385
+ : await dataLoader.load(dataLoaderKey);
386
+ },
387
+
316
388
  /**
317
389
  * Authenticate the request. It check the `Authorization` token value in the request header.
318
390
  * Check the token value & resolve the user by the token.
@@ -369,16 +441,18 @@ module.exports = {
369
441
  createActionResolver(actionName, def = {}) {
370
442
  const {
371
443
  dataLoader: useDataLoader = false,
444
+ objectDataLoader: useObjectDataLoader = false,
372
445
  nullIfError = false,
373
446
  params: staticParams = {},
374
447
  rootParams = {},
375
448
  fileUploadArg = null,
376
449
  } = def;
377
450
  const rootKeys = Object.keys(rootParams);
378
-
379
451
  return async (root, args, context, resolveInfo) => {
380
452
  try {
381
- if (useDataLoader) {
453
+ if(useObjectDataLoader){
454
+ return await this.objectDataLoaderHandler(actionName, staticParams, rootParams, {root, args, context, resolveInfo});
455
+ }else if (useDataLoader) {
382
456
  const dataLoaderMapKey = this.getDataLoaderMapKey(
383
457
  actionName,
384
458
  staticParams,
@@ -566,6 +640,13 @@ module.exports = {
566
640
  },
567
641
  resolvers
568
642
  );
643
+
644
+
645
+ for (const objectName in this.ObjectsUIResolvers) {
646
+ if(resolvers[objectName]){
647
+ resolvers[objectName]['_ui'] = this.ObjectsUIResolvers[objectName];
648
+ }
649
+ }
569
650
  }
570
651
  }
571
652
  }
@@ -810,10 +891,88 @@ module.exports = {
810
891
  }
811
892
  },
812
893
 
894
+ /**
895
+ * Build a DataLoader instance
896
+ *
897
+ * @param {Object} ctx - Moleculer context
898
+ * @param {string} actionName - Fully qualified action name to bind to dataloader
899
+ * @param {string} batchedParamKey - Parameter key to use for loaded values
900
+ * @param {Object} staticParams - Static parameters to use in dataloader
901
+ * @param {Object} args - Arguments passed to GraphQL child resolver
902
+ * @param {Object} [options={}] - Optional arguments
903
+ * @param {Boolean} [options.hashCacheKey=false] - Use a hash for the cacheKeyFn
904
+ * @returns {DataLoader} Dataloader instance
905
+ */
906
+ buildDataLoader(
907
+ ctx,
908
+ actionName,
909
+ batchedParamKey,
910
+ staticParams,
911
+ args,
912
+ { hashCacheKey = false } = {}
913
+ ) {
914
+ const batchLoadFn = keys => {
915
+ const rootParams = { [batchedParamKey]: keys };
916
+ return ctx.call(actionName, _.defaultsDeep({}, args, rootParams, staticParams));
917
+ };
918
+
919
+ const dataLoaderOptions = this.dataLoaderOptions.get(actionName) || {};
920
+ const cacheKeyFn = hashCacheKey && (key => hash(key));
921
+ const options = {
922
+ ...(cacheKeyFn && { cacheKeyFn }),
923
+ cacheMap: new LRUMap(1000),
924
+ ...dataLoaderOptions,
925
+ };
926
+ return new DataLoader(batchLoadFn, options);
927
+ }
928
+
813
929
  },
814
930
  created() {
931
+ this.objectDataLoaders = new Map();
815
932
  this.app = SteedosRouter.staticRouter();
816
933
  },
934
+ events:{
935
+ '@objectRecordEvent.*.*': function(ctx){
936
+ const { objectApiName, isUpdate, isDelete, id, doc } = ctx.params;
937
+ if(objectApiName && (isUpdate || isDelete)){
938
+ const key = this.getObjectDataLoaderMapKey(
939
+ objectApiName
940
+ );
941
+ let dataLoaderKeys = [id];
942
+ if(objectApiName === 'space_users'){
943
+ dataLoaderKeys.push(doc.user)
944
+ }
945
+ const loader = this.objectDataLoaders.get(key);
946
+ if(loader){
947
+ for(const dataLoaderKey of dataLoaderKeys){
948
+ loader.clear(dataLoaderKey);
949
+ }
950
+ }
951
+ }
952
+ },
953
+ 'service-ui.started': function(){
954
+ this.app.use("/", this.express());
955
+ },
956
+ "$packages.changed": function(){
957
+ if (!this.projectStarted) {
958
+ this.projectStarted = true
959
+ // 开发环境, 显示耗时
960
+ if(process.env.NODE_ENV == 'development' && global.__startDate){
961
+ console.log('耗时: ' + (new Date().getTime() - global.__startDate.getTime()) + ' ms');
962
+ }
963
+ console.log(`Project is running at ${process.env.ROOT_URL}`);
964
+ console.log('');
965
+ if (process.env.STEEDOS_AUTO_OPEN_BROWSER != 'false') { // 默认打开,如果不想打开,设置STEEDOS_AUTO_OPEN_BROWSER=false
966
+ try {
967
+ open(process.env.ROOT_URL);
968
+ } catch (error) {
969
+ console.error(error);
970
+ console.error('auto open browser failed.');
971
+ }
972
+ }
973
+ }
974
+ }
975
+ },
817
976
  async started() {
818
977
 
819
978
  this.broker.createService(require("@steedos/service-ui"));
@@ -837,5 +996,14 @@ module.exports = {
837
996
  global.SteedosApi = {
838
997
  express: this.express
839
998
  }
999
+ },
1000
+
1001
+ actions: {
1002
+ getObjectDisplayData: {
1003
+ handler(ctx) {
1004
+ const { objectApiName, recordId, fields } = ctx.params;
1005
+ return this.getObjectDisplayData(objectApiName, recordId, fields);
1006
+ }
1007
+ }
840
1008
  }
841
1009
  };
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@steedos/service-api",
3
- "version": "2.5.5",
3
+ "version": "2.5.6-beta.4",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@steedos/auth": "2.5.5",
8
- "@steedos/router": "2.5.5",
9
- "@steedos/service-object-graphql": "2.5.5",
10
- "@steedos/service-ui": "2.5.5",
7
+ "@steedos/auth": "2.5.6-beta.4",
8
+ "@steedos/router": "2.5.6-beta.4",
9
+ "@steedos/service-object-graphql": "2.5.6-beta.4",
10
+ "@steedos/service-ui": "2.5.6-beta.4",
11
11
  "graphql": "^15.8.0",
12
12
  "graphql-iso-date": "^3.6.1",
13
13
  "graphql-type-json": "^0.3.2",
14
+ "lru_map": "^0.4.1",
14
15
  "moleculer": "^0.14.25",
15
16
  "moleculer-apollo-server": "^0.3.6",
16
17
  "moleculer-web": "^0.10.4",
@@ -20,5 +21,5 @@
20
21
  "publishConfig": {
21
22
  "access": "public"
22
23
  },
23
- "gitHead": "f1e15b667da1e140e3003bf836d56cd6e36c79e3"
24
+ "gitHead": "865682da22058bd2a954753eeeab891612e6b529"
24
25
  }