@steedos/service-api 2.4.4 → 2.4.6

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 +333 -74
  2. package/package.json +5 -5
package/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const ApiGateway = require("moleculer-web");
4
4
  const steedosAuth = require('@steedos/auth');
5
5
  const { ApolloService } = require("moleculer-apollo-server");
6
+ const { MoleculerServerError } = require("moleculer").Errors;
6
7
  const GraphQLJSON = require('graphql-type-json');
7
8
  const {
8
9
  // GraphQLDate,
@@ -12,6 +13,75 @@ const {
12
13
  const SteedosRouter = require('@steedos/router');
13
14
  const _ = require('lodash');
14
15
 
16
+ const mixinOptions = {
17
+
18
+ // Global GraphQL typeDefs
19
+ typeDefs: ['scalar Date', `scalar JSON`],
20
+
21
+ // Global resolvers
22
+ resolvers: {
23
+ JSON: GraphQLJSON,
24
+ Date: GraphQLDateTime
25
+ },
26
+
27
+ // API Gateway route options
28
+ routeOptions: {
29
+ path: "/graphql",
30
+ // cors: true, //会导致response返回 access-control-allow-origin: *, 并进一步导致客户端请求时withCredentials: true参数带来的跨域错误
31
+ whitelist: [
32
+ "graphql"
33
+ ],
34
+ // Route-level Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares
35
+ use: [],
36
+ // Enable authentication. Implement the logic into `authenticate` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authentication
37
+ authentication: true,
38
+ // Enable authorization. Implement the logic into `authorize` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authorization
39
+ authorization: true,
40
+ // The auto-alias feature allows you to declare your route alias directly in your services.
41
+ // The gateway will dynamically build the full routes from service schema.
42
+ autoAliases: true,
43
+ aliases: {},
44
+ // Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
45
+ callingOptions: {},
46
+ bodyParsers: {
47
+ json: {
48
+ strict: false,
49
+ limit: "10MB"
50
+ },
51
+ urlencoded: {
52
+ extended: true,
53
+ limit: "10MB"
54
+ }
55
+ },
56
+ // Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy
57
+ mappingPolicy: "all", // Available values: "all", "restrict"
58
+ // Enable/disable logging
59
+ logging: true,
60
+ // Route error handler
61
+ onError(req, res, err) {
62
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
63
+ res.writeHead(err.code || 500);
64
+ res.end(JSON.stringify(err));
65
+ }
66
+ },
67
+
68
+ // https://www.apollographql.com/docs/apollo-server/v2/api/apollo-server.html
69
+ serverOptions: {
70
+ tracing: process.env.NODE_ENV !== 'production',
71
+
72
+ engine: {
73
+ apiKey: process.env.APOLLO_ENGINE_KEY
74
+ },
75
+ subscriptions: false,
76
+ playground: {
77
+ settings: {
78
+ 'request.credentials': 'same-origin'
79
+ }
80
+ },
81
+ introspection: true
82
+ }
83
+ }
84
+
15
85
  /**
16
86
  * @typedef {import('moleculer').Context} Context Moleculer's Context
17
87
  * @typedef {import('http').IncomingMessage} IncomingRequest Incoming HTTP Request
@@ -21,74 +91,7 @@ module.exports = {
21
91
  name: "api",
22
92
  mixins: [ApiGateway,
23
93
  // GraphQL Apollo Server
24
- ApolloService({
25
-
26
- // Global GraphQL typeDefs
27
- typeDefs: ['scalar Date', `scalar JSON`],
28
-
29
- // Global resolvers
30
- resolvers: {
31
- JSON: GraphQLJSON,
32
- Date: GraphQLDateTime
33
- },
34
-
35
- // API Gateway route options
36
- routeOptions: {
37
- path: "/graphql",
38
- // cors: true, //会导致response返回 access-control-allow-origin: *, 并进一步导致客户端请求时withCredentials: true参数带来的跨域错误
39
- whitelist: [
40
- "graphql"
41
- ],
42
- // Route-level Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares
43
- use: [],
44
- // Enable authentication. Implement the logic into `authenticate` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authentication
45
- authentication: true,
46
- // Enable authorization. Implement the logic into `authorize` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authorization
47
- authorization: true,
48
- // The auto-alias feature allows you to declare your route alias directly in your services.
49
- // The gateway will dynamically build the full routes from service schema.
50
- autoAliases: true,
51
- aliases: {},
52
- // Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
53
- callingOptions: {},
54
- bodyParsers: {
55
- json: {
56
- strict: false,
57
- limit: "10MB"
58
- },
59
- urlencoded: {
60
- extended: true,
61
- limit: "10MB"
62
- }
63
- },
64
- // Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy
65
- mappingPolicy: "all", // Available values: "all", "restrict"
66
- // Enable/disable logging
67
- logging: true,
68
- // Route error handler
69
- onError(req, res, err) {
70
- res.setHeader("Content-Type", "application/json; charset=utf-8");
71
- res.writeHead(err.code || 500);
72
- res.end(JSON.stringify(err));
73
- }
74
- },
75
-
76
- // https://www.apollographql.com/docs/apollo-server/v2/api/apollo-server.html
77
- serverOptions: {
78
- tracing: process.env.NODE_ENV !== 'production',
79
-
80
- engine: {
81
- apiKey: process.env.APOLLO_ENGINE_KEY
82
- },
83
- subscriptions: false,
84
- playground: {
85
- settings: {
86
- 'request.credentials': 'same-origin'
87
- }
88
- },
89
- introspection: true
90
- }
91
- })
94
+ ApolloService(mixinOptions)
92
95
  ],
93
96
 
94
97
  // More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html
@@ -107,7 +110,7 @@ module.exports = {
107
110
  routes: [
108
111
  {
109
112
  aliases: {
110
- "GET /api/health"(req, res){
113
+ "GET /api/health"(req, res) {
111
114
  res.writeHead(200, { 'Content-Type': 'application/json' });
112
115
  res.end(JSON.stringify({ status: 'ok' }));
113
116
  }
@@ -259,7 +262,7 @@ module.exports = {
259
262
  * @param {String} actionName
260
263
  * @param {Object?} def
261
264
  */
262
- createActionResolver(actionName, def = {}) {
265
+ createActionResolver(actionName, def = {}) {
263
266
  const {
264
267
  dataLoader: useDataLoader = false,
265
268
  nullIfError = false,
@@ -361,7 +364,7 @@ module.exports = {
361
364
  );
362
365
  }
363
366
 
364
- return await context.ctx.call(actionName, mergedParams, { meta: {resolveInfo} });
367
+ return await context.ctx.call(actionName, mergedParams, { meta: { resolveInfo } });
365
368
  }
366
369
  } catch (err) {
367
370
  if (nullIfError) {
@@ -376,11 +379,267 @@ module.exports = {
376
379
  };
377
380
  },
378
381
 
382
+ /**
383
+ * Generate GraphQL Schema
384
+ *
385
+ * @param {Object[]} services
386
+ * @returns {Object} Generated schema
387
+ */
388
+ generateGraphQLSchema(services) {
389
+ let str;
390
+ try {
391
+ let typeDefs = [];
392
+ let resolvers = {};
393
+ let schemaDirectives = null;
394
+
395
+ if (mixinOptions.typeDefs) {
396
+ typeDefs = typeDefs.concat(mixinOptions.typeDefs);
397
+ }
398
+
399
+ if (mixinOptions.resolvers) {
400
+ resolvers = _.cloneDeep(mixinOptions.resolvers);
401
+ }
402
+
403
+ if (mixinOptions.schemaDirectives) {
404
+ schemaDirectives = _.cloneDeep(mixinOptions.schemaDirectives);
405
+ }
406
+
407
+ let queries = [];
408
+ let mutations = [];
409
+ let subscriptions = [];
410
+ let types = [];
411
+ let interfaces = [];
412
+ let unions = [];
413
+ let enums = [];
414
+ let inputs = [];
415
+
416
+ const processedServices = new Set();
417
+
418
+ services.forEach(service => {
419
+ const serviceName = this.getServiceName(service);
420
+
421
+ // Skip multiple instances of services
422
+ if (processedServices.has(serviceName)) return;
423
+ processedServices.add(serviceName);
424
+
425
+ if (service.settings && service.settings.graphql) {
426
+ // --- COMPILE SERVICE-LEVEL DEFINITIONS ---
427
+ if (_.isObject(service.settings.graphql)) {
428
+ const globalDef = service.settings.graphql;
429
+
430
+ if (globalDef.query) {
431
+ queries = queries.concat(globalDef.query);
432
+ }
433
+
434
+ if (globalDef.mutation) {
435
+ mutations = mutations.concat(globalDef.mutation);
436
+ }
437
+
438
+ if (globalDef.subscription) {
439
+ subscriptions = subscriptions.concat(globalDef.subscription);
440
+ }
441
+
442
+ if (globalDef.type) {
443
+ types = types.concat(globalDef.type);
444
+ }
445
+
446
+ if (globalDef.interface) {
447
+ interfaces = interfaces.concat(globalDef.interface);
448
+ }
449
+
450
+ if (globalDef.union) {
451
+ unions = unions.concat(globalDef.union);
452
+ }
453
+
454
+ if (globalDef.enum) {
455
+ enums = enums.concat(globalDef.enum);
456
+ }
457
+
458
+ if (globalDef.input) {
459
+ inputs = inputs.concat(globalDef.input);
460
+ }
461
+
462
+ if (globalDef.resolvers) {
463
+ resolvers = Object.entries(globalDef.resolvers).reduce(
464
+ (acc, [name, resolver]) => {
465
+ acc[name] = _.merge(
466
+ acc[name] || {},
467
+ this.createServiceResolvers(serviceName, resolver)
468
+ );
469
+ return acc;
470
+ },
471
+ resolvers
472
+ );
473
+ }
474
+ }
475
+ }
476
+
477
+ // --- COMPILE ACTION-LEVEL DEFINITIONS ---
478
+ const resolver = {};
479
+
480
+ Object.values(service.actions).forEach(action => {
481
+ const { graphql: def } = action;
482
+ if (
483
+ mixinOptions.checkActionVisibility &&
484
+ action.visibility != null &&
485
+ action.visibility != "published"
486
+ )
487
+ return;
488
+
489
+ if (def && _.isObject(def)) {
490
+ if (def.query) {
491
+ if (!resolver["Query"]) resolver.Query = {};
492
+
493
+ _.castArray(def.query).forEach(query => {
494
+ const name = this.getFieldName(query);
495
+ queries.push(query);
496
+ resolver.Query[name] = this.createActionResolver(
497
+ action.name
498
+ );
499
+ });
500
+ }
501
+
502
+ if (def.mutation) {
503
+ if (!resolver["Mutation"]) resolver.Mutation = {};
504
+
505
+ _.castArray(def.mutation).forEach(mutation => {
506
+ const name = this.getFieldName(mutation);
507
+ mutations.push(mutation);
508
+ resolver.Mutation[name] = this.createActionResolver(
509
+ action.name,
510
+ {
511
+ fileUploadArg: def.fileUploadArg,
512
+ }
513
+ );
514
+ });
515
+ }
516
+
517
+ if (def.subscription) {
518
+ if (!resolver["Subscription"]) resolver.Subscription = {};
519
+
520
+ _.castArray(def.subscription).forEach(subscription => {
521
+ const name = this.getFieldName(subscription);
522
+ subscriptions.push(subscription);
523
+ resolver.Subscription[name] =
524
+ this.createAsyncIteratorResolver(
525
+ action.name,
526
+ def.tags,
527
+ def.filter
528
+ );
529
+ });
530
+ }
531
+
532
+ if (def.type) {
533
+ types = types.concat(def.type);
534
+ }
535
+
536
+ if (def.interface) {
537
+ interfaces = interfaces.concat(def.interface);
538
+ }
539
+
540
+ if (def.union) {
541
+ unions = unions.concat(def.union);
542
+ }
543
+
544
+ if (def.enum) {
545
+ enums = enums.concat(def.enum);
546
+ }
547
+
548
+ if (def.input) {
549
+ inputs = inputs.concat(def.input);
550
+ }
551
+ }
552
+ });
553
+
554
+ if (Object.keys(resolver).length > 0) {
555
+ resolvers = _.merge(resolvers, resolver);
556
+ }
557
+ });
558
+
559
+ if (
560
+ queries.length > 0 ||
561
+ types.length > 0 ||
562
+ mutations.length > 0 ||
563
+ subscriptions.length > 0 ||
564
+ interfaces.length > 0 ||
565
+ unions.length > 0 ||
566
+ enums.length > 0 ||
567
+ inputs.length > 0
568
+ ) {
569
+ str = "";
570
+ if (queries.length > 0) {
571
+ str += `
572
+ type Query {
573
+ ${queries.join("\n")}
574
+ }
575
+ `;
576
+ }
577
+
578
+ if (mutations.length > 0) {
579
+ str += `
580
+ type Mutation {
581
+ ${mutations.join("\n")}
582
+ }
583
+ `;
584
+ }
585
+
586
+ if (subscriptions.length > 0) {
587
+ str += `
588
+ type Subscription {
589
+ ${subscriptions.join("\n")}
590
+ }
591
+ `;
592
+ }
593
+
594
+ if (types.length > 0) {
595
+ str += `
596
+ ${types.join("\n")}
597
+ `;
598
+ }
599
+
600
+ if (interfaces.length > 0) {
601
+ str += `
602
+ ${interfaces.join("\n")}
603
+ `;
604
+ }
605
+
606
+ if (unions.length > 0) {
607
+ str += `
608
+ ${unions.join("\n")}
609
+ `;
610
+ }
611
+
612
+ if (enums.length > 0) {
613
+ str += `
614
+ ${enums.join("\n")}
615
+ `;
616
+ }
617
+
618
+ if (inputs.length > 0) {
619
+ str += `
620
+ ${inputs.join("\n")}
621
+ `;
622
+ }
623
+
624
+ typeDefs.push(str);
625
+ }
626
+
627
+ return this.makeExecutableSchema({ typeDefs, resolvers, schemaDirectives });
628
+ } catch (err) {
629
+ throw new MoleculerServerError(
630
+ "Unable to compile GraphQL schema",
631
+ 500,
632
+ "UNABLE_COMPILE_GRAPHQL_SCHEMA",
633
+ { err }
634
+ );
635
+ }
636
+ },
637
+
379
638
  },
380
- created(){
639
+ created() {
381
640
  this.app = SteedosRouter.staticRouter();
382
641
  },
383
- async started (){
642
+ async started() {
384
643
 
385
644
  this.broker.createService(require("@steedos/service-ui"));
386
645
 
@@ -396,7 +655,7 @@ module.exports = {
396
655
  // });
397
656
  // }
398
657
 
399
- this.broker.waitForServices('~packages-@steedos/service-ui').then(()=>{
658
+ this.broker.waitForServices('~packages-@steedos/service-ui').then(() => {
400
659
  this.app.use("/", this.express());
401
660
  })
402
661
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@steedos/service-api",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@steedos/auth": "2.4.4",
8
- "@steedos/router": "2.4.4",
9
- "@steedos/service-ui": "2.4.4",
7
+ "@steedos/auth": "2.4.6",
8
+ "@steedos/router": "2.4.6",
9
+ "@steedos/service-ui": "2.4.6",
10
10
  "graphql": "^15.8.0",
11
11
  "graphql-iso-date": "^3.6.1",
12
12
  "graphql-type-json": "^0.3.2",
@@ -18,5 +18,5 @@
18
18
  "publishConfig": {
19
19
  "access": "public"
20
20
  },
21
- "gitHead": "04caa0eb2226278f96c01825dd2828170c1f9789"
21
+ "gitHead": "baf2b26509111eb49b56ea563ce9b86fde8753f7"
22
22
  }