sofa-api 0.15.4 → 0.16.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.
Files changed (3) hide show
  1. package/index.js +106 -76
  2. package/index.mjs +107 -77
  3. package/package.json +3 -3
package/index.js CHANGED
@@ -73,12 +73,12 @@ function resolveVariable({ value, type, schema, }) {
73
73
  }
74
74
  }
75
75
 
76
- var _a;
76
+ var _a, _b;
77
77
  const levels = ['error', 'warn', 'info', 'debug'];
78
78
  const toLevel = (string) => levels.includes(string) ? string : null;
79
- const currentLevel = process.env.SOFA_DEBUG
79
+ const currentLevel = ((_a = globalThis.process) === null || _a === void 0 ? void 0 : _a.env.SOFA_DEBUG)
80
80
  ? 'debug'
81
- : (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
81
+ : (_b = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _b !== void 0 ? _b : 'info';
82
82
  const log = (level, color, args) => {
83
83
  if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
84
84
  console.log(`${color(level)}:`, ...args);
@@ -275,6 +275,34 @@ class SubscriptionManager {
275
275
  }
276
276
  }
277
277
 
278
+ const defaultErrorHandler = (errors) => {
279
+ var _a;
280
+ let status;
281
+ const headers = {
282
+ 'Content-Type': 'application/json; charset=utf-8',
283
+ };
284
+ for (const error of errors) {
285
+ if (typeof error === 'object' &&
286
+ error != null &&
287
+ ((_a = error.extensions) === null || _a === void 0 ? void 0 : _a.http)) {
288
+ if (error.extensions.http.status &&
289
+ (!status || error.extensions.http.status > status)) {
290
+ status = error.extensions.http.status;
291
+ }
292
+ if (error.extensions.http.headers) {
293
+ Object.assign(headers, error.extensions.http.headers);
294
+ }
295
+ delete error.extensions.http;
296
+ }
297
+ }
298
+ if (!status) {
299
+ status = 500;
300
+ }
301
+ return new router.Response(JSON.stringify({ errors }), {
302
+ status,
303
+ headers,
304
+ });
305
+ };
278
306
  function createRouter(sofa) {
279
307
  logger.debug('[Sofa] Creating router');
280
308
  const router$1 = router.createRouter({
@@ -451,82 +479,79 @@ function createMutationRoute({ sofa, router, fieldName, }) {
451
479
  function useHandler(config) {
452
480
  const { sofa, operation, fieldName } = config;
453
481
  const info = config.info;
482
+ const errorHandler = sofa.errorHandler || defaultErrorHandler;
454
483
  return (request, serverContext) => tslib.__awaiter(this, void 0, void 0, function* () {
455
- var _a;
456
- let body = {};
457
- if (request.body != null) {
458
- const strBody = yield request.text();
459
- if (strBody) {
460
- body = JSON.parse(strBody);
484
+ var _a, _b;
485
+ try {
486
+ let body = {};
487
+ if (request.body != null) {
488
+ const strBody = yield request.text();
489
+ if (strBody) {
490
+ try {
491
+ body = JSON.parse(strBody);
492
+ }
493
+ catch (error) {
494
+ throw utils.createGraphQLError('POST body sent invalid JSON.', {
495
+ extensions: {
496
+ http: {
497
+ status: 400,
498
+ }
499
+ }
500
+ });
501
+ }
502
+ }
461
503
  }
462
- }
463
- const variableValues = info.variables.reduce((variables, variable) => {
464
- const name = variable.variable.name.value;
465
- const value = parseVariable({
466
- value: pickParam({
467
- url: request.url,
468
- body,
469
- params: request.params || {},
470
- name,
471
- }),
472
- variable,
473
- schema: sofa.schema,
474
- });
475
- if (typeof value === 'undefined') {
476
- return variables;
504
+ let variableValues = {};
505
+ try {
506
+ variableValues = info.variables.reduce((variables, variable) => {
507
+ const name = variable.variable.name.value;
508
+ const value = parseVariable({
509
+ value: pickParam({
510
+ url: request.url,
511
+ body,
512
+ params: request.params || {},
513
+ name,
514
+ }),
515
+ variable,
516
+ schema: sofa.schema,
517
+ });
518
+ if (typeof value === 'undefined') {
519
+ return variables;
520
+ }
521
+ return Object.assign(Object.assign({}, variables), { [name]: value });
522
+ }, {});
477
523
  }
478
- return Object.assign(Object.assign({}, variables), { [name]: value });
479
- }, {});
480
- const sofaServerContext = Object.assign(Object.assign({}, serverContext), { request });
481
- const contextValue = yield sofa.contextFactory(sofaServerContext);
482
- const result = yield sofa.execute({
483
- schema: sofa.schema,
484
- document: operation,
485
- contextValue,
486
- variableValues,
487
- operationName: info.operation.name && info.operation.name.value,
488
- });
489
- if (result.errors) {
490
- const defaultErrorHandler = (errors) => {
491
- var _a;
492
- let status;
493
- const headers = {
494
- 'Content-Type': 'application/json; charset=utf-8',
495
- };
496
- for (const error of errors) {
497
- if (typeof error === 'object' && error != null && ((_a = error.extensions) === null || _a === void 0 ? void 0 : _a.http)) {
498
- if (error.extensions.http.status &&
499
- (!status || error.extensions.http.status > status)) {
500
- status = error.extensions.http.status;
501
- }
502
- if (error.extensions.http.headers) {
503
- Object.assign(headers, error.extensions.http.headers);
524
+ catch (error) {
525
+ throw utils.createGraphQLError(error.message || ((_a = error.toString) === null || _a === void 0 ? void 0 : _a.call(error)) || error, {
526
+ extensions: {
527
+ http: {
528
+ status: 400,
504
529
  }
505
530
  }
506
- }
507
- if (!status) {
508
- status = 500;
509
- }
510
- if (errors.length === 1) {
511
- return new router.Response(JSON.stringify(errors[0]), {
512
- status,
513
- headers,
514
- });
515
- }
516
- return new router.Response(JSON.stringify({ errors }), {
517
- status,
518
- headers,
519
531
  });
520
- };
521
- const errorHandler = sofa.errorHandler || defaultErrorHandler;
522
- return errorHandler(result.errors);
532
+ }
533
+ const sofaServerContext = Object.assign(Object.assign({}, serverContext), { request });
534
+ const contextValue = yield sofa.contextFactory(sofaServerContext);
535
+ const result = yield sofa.execute({
536
+ schema: sofa.schema,
537
+ document: operation,
538
+ contextValue,
539
+ variableValues,
540
+ operationName: info.operation.name && info.operation.name.value,
541
+ });
542
+ if (result.errors) {
543
+ return errorHandler(result.errors);
544
+ }
545
+ return new router.Response(JSON.stringify((_b = result.data) === null || _b === void 0 ? void 0 : _b[fieldName]), {
546
+ status: config.route.responseStatus,
547
+ headers: {
548
+ 'Content-Type': 'application/json',
549
+ },
550
+ });
551
+ }
552
+ catch (error) {
553
+ return errorHandler([error]);
523
554
  }
524
- return new router.Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
525
- status: config.route.responseStatus,
526
- headers: {
527
- 'Content-Type': 'application/json',
528
- },
529
- });
530
555
  });
531
556
  }
532
557
  function getPath(fieldName, hasId = false) {
@@ -579,6 +604,7 @@ function isContextFn(context) {
579
604
  // they might represent an Object that is not a model
580
605
  // We check it later, when an operation is being built
581
606
  function extractsModels(schema) {
607
+ var _a, _b;
582
608
  const modelMap = {};
583
609
  const query = schema.getQueryType();
584
610
  const fields = query.getFields();
@@ -598,7 +624,7 @@ function extractsModels(schema) {
598
624
  // add to registry with `list: true`
599
625
  const sameName = isNameEqual(field.name, namedType.name + 's');
600
626
  const allOptionalArguments = !field.args.some((arg) => graphql.isNonNullType(arg.type));
601
- modelMap[namedType.name].list = sameName && allOptionalArguments;
627
+ (_a = modelMap[namedType.name]).list || (_a.list = sameName && allOptionalArguments);
602
628
  }
603
629
  else if (graphql.isObjectType(field.type) ||
604
630
  (graphql.isNonNullType(field.type) && graphql.isObjectType(field.type.ofType))) {
@@ -608,7 +634,7 @@ function extractsModels(schema) {
608
634
  // add to registry with `single: true`
609
635
  const sameName = isNameEqual(field.name, namedType.name);
610
636
  const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
611
- modelMap[namedType.name].single = sameName && hasIdArgument;
637
+ (_b = modelMap[namedType.name]).single || (_b.single = sameName && hasIdArgument);
612
638
  }
613
639
  }
614
640
  }
@@ -715,20 +741,24 @@ function buildPathFromOperation({ url, schema, operation, useRequestBody, tags,
715
741
  const info = getOperationInfo(operation);
716
742
  const enumTypes = resolveEnumTypes(schema);
717
743
  const summary = resolveDescription(schema, info.operation);
744
+ const variables = info.operation.variableDefinitions;
745
+ const pathParams = variables === null || variables === void 0 ? void 0 : variables.filter((variable) => isInPath(url, variable.variable.name.value));
746
+ const bodyParams = variables === null || variables === void 0 ? void 0 : variables.filter((variable) => !isInPath(url, variable.variable.name.value));
718
747
  return Object.assign(Object.assign({ tags,
719
748
  description,
720
749
  summary, operationId: info.name }, (useRequestBody
721
750
  ? {
751
+ parameters: resolveParameters(url, pathParams, schema, info.operation, { customScalars, enumTypes }),
722
752
  requestBody: {
723
753
  content: {
724
754
  'application/json': {
725
- schema: resolveRequestBody(info.operation.variableDefinitions, schema, info.operation, { customScalars, enumTypes }),
755
+ schema: resolveRequestBody(bodyParams, schema, info.operation, { customScalars, enumTypes }),
726
756
  },
727
757
  },
728
758
  },
729
759
  }
730
760
  : {
731
- parameters: resolveParameters(url, info.operation.variableDefinitions, schema, info.operation, { customScalars, enumTypes }),
761
+ parameters: resolveParameters(url, variables, schema, info.operation, { customScalars, enumTypes }),
732
762
  })), { responses: {
733
763
  200: {
734
764
  description: summary,
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { __awaiter, __asyncValues } from 'tslib';
2
2
  import { getOperationAST, Kind, isScalarType, isEqualType, GraphQLBoolean, isInputObjectType, isObjectType, isNonNullType, execute, subscribe, getNamedType, isListType, isEnumType, parse, printType, isIntrospectionType } from 'graphql';
3
- import { buildOperationNodeForField } from '@graphql-tools/utils';
3
+ import { buildOperationNodeForField, createGraphQLError } from '@graphql-tools/utils';
4
4
  import { paramCase } from 'param-case';
5
5
  import { crypto, fetch } from '@whatwg-node/fetch';
6
6
  import colors from 'ansi-colors';
@@ -67,12 +67,12 @@ function resolveVariable({ value, type, schema, }) {
67
67
  }
68
68
  }
69
69
 
70
- var _a;
70
+ var _a, _b;
71
71
  const levels = ['error', 'warn', 'info', 'debug'];
72
72
  const toLevel = (string) => levels.includes(string) ? string : null;
73
- const currentLevel = process.env.SOFA_DEBUG
73
+ const currentLevel = ((_a = globalThis.process) === null || _a === void 0 ? void 0 : _a.env.SOFA_DEBUG)
74
74
  ? 'debug'
75
- : (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
75
+ : (_b = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _b !== void 0 ? _b : 'info';
76
76
  const log = (level, color, args) => {
77
77
  if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
78
78
  console.log(`${color(level)}:`, ...args);
@@ -269,6 +269,34 @@ class SubscriptionManager {
269
269
  }
270
270
  }
271
271
 
272
+ const defaultErrorHandler = (errors) => {
273
+ var _a;
274
+ let status;
275
+ const headers = {
276
+ 'Content-Type': 'application/json; charset=utf-8',
277
+ };
278
+ for (const error of errors) {
279
+ if (typeof error === 'object' &&
280
+ error != null &&
281
+ ((_a = error.extensions) === null || _a === void 0 ? void 0 : _a.http)) {
282
+ if (error.extensions.http.status &&
283
+ (!status || error.extensions.http.status > status)) {
284
+ status = error.extensions.http.status;
285
+ }
286
+ if (error.extensions.http.headers) {
287
+ Object.assign(headers, error.extensions.http.headers);
288
+ }
289
+ delete error.extensions.http;
290
+ }
291
+ }
292
+ if (!status) {
293
+ status = 500;
294
+ }
295
+ return new Response(JSON.stringify({ errors }), {
296
+ status,
297
+ headers,
298
+ });
299
+ };
272
300
  function createRouter(sofa) {
273
301
  logger.debug('[Sofa] Creating router');
274
302
  const router = createRouter$1({
@@ -445,82 +473,79 @@ function createMutationRoute({ sofa, router, fieldName, }) {
445
473
  function useHandler(config) {
446
474
  const { sofa, operation, fieldName } = config;
447
475
  const info = config.info;
476
+ const errorHandler = sofa.errorHandler || defaultErrorHandler;
448
477
  return (request, serverContext) => __awaiter(this, void 0, void 0, function* () {
449
- var _a;
450
- let body = {};
451
- if (request.body != null) {
452
- const strBody = yield request.text();
453
- if (strBody) {
454
- body = JSON.parse(strBody);
478
+ var _a, _b;
479
+ try {
480
+ let body = {};
481
+ if (request.body != null) {
482
+ const strBody = yield request.text();
483
+ if (strBody) {
484
+ try {
485
+ body = JSON.parse(strBody);
486
+ }
487
+ catch (error) {
488
+ throw createGraphQLError('POST body sent invalid JSON.', {
489
+ extensions: {
490
+ http: {
491
+ status: 400,
492
+ }
493
+ }
494
+ });
495
+ }
496
+ }
455
497
  }
456
- }
457
- const variableValues = info.variables.reduce((variables, variable) => {
458
- const name = variable.variable.name.value;
459
- const value = parseVariable({
460
- value: pickParam({
461
- url: request.url,
462
- body,
463
- params: request.params || {},
464
- name,
465
- }),
466
- variable,
467
- schema: sofa.schema,
468
- });
469
- if (typeof value === 'undefined') {
470
- return variables;
498
+ let variableValues = {};
499
+ try {
500
+ variableValues = info.variables.reduce((variables, variable) => {
501
+ const name = variable.variable.name.value;
502
+ const value = parseVariable({
503
+ value: pickParam({
504
+ url: request.url,
505
+ body,
506
+ params: request.params || {},
507
+ name,
508
+ }),
509
+ variable,
510
+ schema: sofa.schema,
511
+ });
512
+ if (typeof value === 'undefined') {
513
+ return variables;
514
+ }
515
+ return Object.assign(Object.assign({}, variables), { [name]: value });
516
+ }, {});
471
517
  }
472
- return Object.assign(Object.assign({}, variables), { [name]: value });
473
- }, {});
474
- const sofaServerContext = Object.assign(Object.assign({}, serverContext), { request });
475
- const contextValue = yield sofa.contextFactory(sofaServerContext);
476
- const result = yield sofa.execute({
477
- schema: sofa.schema,
478
- document: operation,
479
- contextValue,
480
- variableValues,
481
- operationName: info.operation.name && info.operation.name.value,
482
- });
483
- if (result.errors) {
484
- const defaultErrorHandler = (errors) => {
485
- var _a;
486
- let status;
487
- const headers = {
488
- 'Content-Type': 'application/json; charset=utf-8',
489
- };
490
- for (const error of errors) {
491
- if (typeof error === 'object' && error != null && ((_a = error.extensions) === null || _a === void 0 ? void 0 : _a.http)) {
492
- if (error.extensions.http.status &&
493
- (!status || error.extensions.http.status > status)) {
494
- status = error.extensions.http.status;
495
- }
496
- if (error.extensions.http.headers) {
497
- Object.assign(headers, error.extensions.http.headers);
518
+ catch (error) {
519
+ throw createGraphQLError(error.message || ((_a = error.toString) === null || _a === void 0 ? void 0 : _a.call(error)) || error, {
520
+ extensions: {
521
+ http: {
522
+ status: 400,
498
523
  }
499
524
  }
500
- }
501
- if (!status) {
502
- status = 500;
503
- }
504
- if (errors.length === 1) {
505
- return new Response(JSON.stringify(errors[0]), {
506
- status,
507
- headers,
508
- });
509
- }
510
- return new Response(JSON.stringify({ errors }), {
511
- status,
512
- headers,
513
525
  });
514
- };
515
- const errorHandler = sofa.errorHandler || defaultErrorHandler;
516
- return errorHandler(result.errors);
526
+ }
527
+ const sofaServerContext = Object.assign(Object.assign({}, serverContext), { request });
528
+ const contextValue = yield sofa.contextFactory(sofaServerContext);
529
+ const result = yield sofa.execute({
530
+ schema: sofa.schema,
531
+ document: operation,
532
+ contextValue,
533
+ variableValues,
534
+ operationName: info.operation.name && info.operation.name.value,
535
+ });
536
+ if (result.errors) {
537
+ return errorHandler(result.errors);
538
+ }
539
+ return new Response(JSON.stringify((_b = result.data) === null || _b === void 0 ? void 0 : _b[fieldName]), {
540
+ status: config.route.responseStatus,
541
+ headers: {
542
+ 'Content-Type': 'application/json',
543
+ },
544
+ });
545
+ }
546
+ catch (error) {
547
+ return errorHandler([error]);
517
548
  }
518
- return new Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
519
- status: config.route.responseStatus,
520
- headers: {
521
- 'Content-Type': 'application/json',
522
- },
523
- });
524
549
  });
525
550
  }
526
551
  function getPath(fieldName, hasId = false) {
@@ -573,6 +598,7 @@ function isContextFn(context) {
573
598
  // they might represent an Object that is not a model
574
599
  // We check it later, when an operation is being built
575
600
  function extractsModels(schema) {
601
+ var _a, _b;
576
602
  const modelMap = {};
577
603
  const query = schema.getQueryType();
578
604
  const fields = query.getFields();
@@ -592,7 +618,7 @@ function extractsModels(schema) {
592
618
  // add to registry with `list: true`
593
619
  const sameName = isNameEqual(field.name, namedType.name + 's');
594
620
  const allOptionalArguments = !field.args.some((arg) => isNonNullType(arg.type));
595
- modelMap[namedType.name].list = sameName && allOptionalArguments;
621
+ (_a = modelMap[namedType.name]).list || (_a.list = sameName && allOptionalArguments);
596
622
  }
597
623
  else if (isObjectType(field.type) ||
598
624
  (isNonNullType(field.type) && isObjectType(field.type.ofType))) {
@@ -602,7 +628,7 @@ function extractsModels(schema) {
602
628
  // add to registry with `single: true`
603
629
  const sameName = isNameEqual(field.name, namedType.name);
604
630
  const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
605
- modelMap[namedType.name].single = sameName && hasIdArgument;
631
+ (_b = modelMap[namedType.name]).single || (_b.single = sameName && hasIdArgument);
606
632
  }
607
633
  }
608
634
  }
@@ -709,20 +735,24 @@ function buildPathFromOperation({ url, schema, operation, useRequestBody, tags,
709
735
  const info = getOperationInfo(operation);
710
736
  const enumTypes = resolveEnumTypes(schema);
711
737
  const summary = resolveDescription(schema, info.operation);
738
+ const variables = info.operation.variableDefinitions;
739
+ const pathParams = variables === null || variables === void 0 ? void 0 : variables.filter((variable) => isInPath(url, variable.variable.name.value));
740
+ const bodyParams = variables === null || variables === void 0 ? void 0 : variables.filter((variable) => !isInPath(url, variable.variable.name.value));
712
741
  return Object.assign(Object.assign({ tags,
713
742
  description,
714
743
  summary, operationId: info.name }, (useRequestBody
715
744
  ? {
745
+ parameters: resolveParameters(url, pathParams, schema, info.operation, { customScalars, enumTypes }),
716
746
  requestBody: {
717
747
  content: {
718
748
  'application/json': {
719
- schema: resolveRequestBody(info.operation.variableDefinitions, schema, info.operation, { customScalars, enumTypes }),
749
+ schema: resolveRequestBody(bodyParams, schema, info.operation, { customScalars, enumTypes }),
720
750
  },
721
751
  },
722
752
  },
723
753
  }
724
754
  : {
725
- parameters: resolveParameters(url, info.operation.variableDefinitions, schema, info.operation, { customScalars, enumTypes }),
755
+ parameters: resolveParameters(url, variables, schema, info.operation, { customScalars, enumTypes }),
726
756
  })), { responses: {
727
757
  200: {
728
758
  description: summary,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "sofa-api",
3
- "version": "0.15.4",
3
+ "version": "0.16.0",
4
4
  "description": "Create REST APIs with GraphQL",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "graphql": "^0.13.2 || ^14.0.0 || ^15.0.0 || ^16.0.0"
7
+ "graphql": "^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
10
10
  "@graphql-tools/utils": "9.1.4",
@@ -14,7 +14,7 @@
14
14
  "openapi-types": "12.1.0",
15
15
  "param-case": "3.0.4",
16
16
  "title-case": "3.0.3",
17
- "tslib": "2.4.1"
17
+ "tslib": "2.5.0"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",