sofa-api 0.12.0-alpha.0 → 0.12.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 CHANGED
@@ -341,6 +341,66 @@ app.use(
341
341
  writeFileSync('./swagger.json', JSON.stringify(openApi.get(), null, 2));
342
342
  ```
343
343
 
344
+ OpenAPI (Swagger) with custom tags, summary and description
345
+
346
+ ```ts
347
+ const openApi = OpenAPI({
348
+ schema,
349
+ info: {
350
+ title: 'Example API',
351
+ version: '3.0.0',
352
+ },
353
+ tags: [
354
+ {
355
+ name: 'Book',
356
+ description: 'Book related operations',
357
+ },
358
+ {
359
+ name: 'Author',
360
+ description: 'Author related operations',
361
+ },
362
+ ],
363
+ });
364
+ ```
365
+
366
+ ```ts
367
+ @Resolver(Book)
368
+ export class BookResolver {
369
+ @Query(() => Book, { description: 'Get book by id' }) // custom summary tag
370
+ getBookById(@Arg('id', () => Int) id: number) {
371
+ return 'book';
372
+ }
373
+ }
374
+ ```
375
+
376
+ ```ts
377
+ const routes: SofaConfig['routes'] = {
378
+ 'Query.getBookById': {
379
+ method: 'POST',
380
+ path: '/book/:id',
381
+ tags: ['Book'],
382
+ description: 'This is a custom detailed description for getBookById',
383
+ },
384
+ }
385
+
386
+ const createSofaMiddleware = (
387
+ schema: GraphQLSchema,
388
+ openApi: ReturnType<typeof OpenAPI>,
389
+ basePath = ''
390
+ ): ReturnType<typeof useSofa> => {
391
+ return useSofa({
392
+ routes,
393
+ basePath,
394
+ schema,
395
+ onRoute(info) {
396
+ openApi.addRoute(info, { basePath });
397
+ },
398
+ });
399
+ };
400
+ // writes every recorder route
401
+ openApi.save('./swagger.yml');
402
+ ```
403
+
344
404
  ## License
345
405
 
346
406
  [MIT](https://github.com/Urigo/sofa/blob/master/LICENSE) © Uri Goldshtein
package/index.js CHANGED
@@ -9,7 +9,6 @@ const graphql = require('graphql');
9
9
  const ittyRouter = require('itty-router');
10
10
  const utils = require('@graphql-tools/utils');
11
11
  const paramCase = require('param-case');
12
- const uuid = require('uuid');
13
12
  const fetch = require('@whatwg-node/fetch');
14
13
  const colors = _interopDefault(require('ansi-colors'));
15
14
  const server = require('@whatwg-node/server');
@@ -114,20 +113,17 @@ class SubscriptionManager {
114
113
  }
115
114
  start(event, contextValue) {
116
115
  return tslib.__awaiter(this, void 0, void 0, function* () {
117
- const id = uuid.v4();
116
+ const id = fetch.crypto.randomUUID();
118
117
  const name = event.subscription;
119
118
  if (!this.operations.has(name)) {
120
119
  throw new Error(`Subscription '${name}' is not available`);
121
120
  }
122
- const { document, operationName, variables } = this.operations.get(name);
123
121
  logger.info(`[Subscription] Start ${id}`, event);
124
122
  const result = yield this.execute({
125
123
  id,
126
124
  name,
127
125
  url: event.url,
128
- document,
129
- operationName,
130
- variables,
126
+ variables: event.variables,
131
127
  contextValue,
132
128
  });
133
129
  if (typeof result !== 'undefined') {
@@ -166,9 +162,9 @@ class SubscriptionManager {
166
162
  }, contextValue);
167
163
  });
168
164
  }
169
- execute({ id, document, name, url, operationName, variables, contextValue, }) {
165
+ execute({ id, name, url, variables, contextValue, }) {
170
166
  return tslib.__awaiter(this, void 0, void 0, function* () {
171
- const variableNodes = this.operations.get(name).variables;
167
+ const { document, operationName, variables: variableNodes } = this.operations.get(name);
172
168
  const variableValues = variableNodes.reduce((values, variable) => {
173
169
  const value = parseVariable({
174
170
  value: variables[variable.variable.name.value],
@@ -178,7 +174,7 @@ class SubscriptionManager {
178
174
  if (typeof value === 'undefined') {
179
175
  return values;
180
176
  }
181
- return Object.assign(Object.assign({}, values), { [name]: value });
177
+ return Object.assign(Object.assign({}, values), { [variable.variable.name.value]: value });
182
178
  }, {});
183
179
  const execution = yield this.sofa.subscribe({
184
180
  schema: this.sofa.schema,
@@ -371,7 +367,7 @@ function createRouter(sofa) {
371
367
  return router;
372
368
  }
373
369
  function createQueryRoute({ sofa, router, fieldName, }) {
374
- var _a, _b, _c, _d;
370
+ var _a, _b, _c, _d, _e, _f;
375
371
  logger.debug(`[Router] Creating ${fieldName} query`);
376
372
  const queryType = sofa.schema.getQueryType();
377
373
  const operationNode = utils.buildOperationNodeForField({
@@ -405,6 +401,8 @@ function createQueryRoute({ sofa, router, fieldName, }) {
405
401
  document: operation,
406
402
  path: route.path,
407
403
  method: route.method.toUpperCase(),
404
+ tags: (_e = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.tags) !== null && _e !== void 0 ? _e : [],
405
+ description: (_f = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.description) !== null && _f !== void 0 ? _f : '',
408
406
  };
409
407
  }
410
408
  function createMutationRoute({ sofa, router, fieldName, }) {
@@ -438,6 +436,8 @@ function createMutationRoute({ sofa, router, fieldName, }) {
438
436
  document: operation,
439
437
  path,
440
438
  method,
439
+ tags: (routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.tags) || [],
440
+ description: (routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.description) || '',
441
441
  };
442
442
  }
443
443
  function useHandler(config) {
@@ -619,7 +619,7 @@ function mapToRef(type) {
619
619
  return `#/components/schemas/${type}`;
620
620
  }
621
621
 
622
- function buildSchemaObjectFromType(type) {
622
+ function buildSchemaObjectFromType(type, opts) {
623
623
  const required = [];
624
624
  const properties = {};
625
625
  const fields = type.getFields();
@@ -628,28 +628,27 @@ function buildSchemaObjectFromType(type) {
628
628
  if (graphql.isNonNullType(field.type)) {
629
629
  required.push(field.name);
630
630
  }
631
- properties[fieldName] = resolveField(field);
631
+ properties[fieldName] = resolveField(field, opts);
632
632
  if (field.description) {
633
633
  properties[fieldName].description = field.description;
634
634
  }
635
635
  }
636
636
  return Object.assign(Object.assign(Object.assign({ type: 'object' }, (required.length ? { required } : {})), { properties }), (type.description ? { description: type.description } : {}));
637
637
  }
638
- function resolveField(field) {
639
- return resolveFieldType(field.type);
638
+ function resolveField(field, opts) {
639
+ return resolveFieldType(field.type, opts);
640
640
  }
641
641
  // array -> [type]
642
642
  // type -> $ref
643
643
  // scalar -> swagger primitive
644
- function resolveFieldType(type) {
645
- var _a, _b;
644
+ function resolveFieldType(type, opts) {
646
645
  if (graphql.isNonNullType(type)) {
647
- return resolveFieldType(type.ofType);
646
+ return resolveFieldType(type.ofType, opts);
648
647
  }
649
648
  if (graphql.isListType(type)) {
650
649
  return {
651
650
  type: 'array',
652
- items: resolveFieldType(type.ofType),
651
+ items: resolveFieldType(type.ofType, opts),
653
652
  };
654
653
  }
655
654
  if (graphql.isObjectType(type)) {
@@ -658,14 +657,15 @@ function resolveFieldType(type) {
658
657
  };
659
658
  }
660
659
  if (graphql.isScalarType(type)) {
661
- return (mapToPrimitive(type.name) || {
660
+ return (mapToPrimitive(type.name) ||
661
+ opts.customScalars[type.name] || {
662
662
  type: 'object',
663
663
  });
664
664
  }
665
665
  if (graphql.isEnumType(type)) {
666
666
  return {
667
667
  type: 'string',
668
- enum: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.map((value) => value.name.value),
668
+ enum: type.getValues().map((value) => value.name),
669
669
  };
670
670
  }
671
671
  return {
@@ -673,10 +673,12 @@ function resolveFieldType(type) {
673
673
  };
674
674
  }
675
675
 
676
- function buildPathFromOperation({ url, schema, operation, useRequestBody, }) {
676
+ function buildPathFromOperation({ url, schema, operation, useRequestBody, tags, description, customScalars, }) {
677
677
  const info = getOperationInfo(operation);
678
- const description = resolveDescription(schema, info.operation);
679
- return Object.assign(Object.assign({ operationId: info.name }, (useRequestBody
678
+ const summary = resolveSummary(schema, info.operation);
679
+ return Object.assign(Object.assign({ tags,
680
+ description,
681
+ summary, operationId: info.name }, (useRequestBody
680
682
  ? {
681
683
  requestBody: {
682
684
  content: {
@@ -690,12 +692,13 @@ function buildPathFromOperation({ url, schema, operation, useRequestBody, }) {
690
692
  parameters: resolveParameters(url, info.operation.variableDefinitions),
691
693
  })), { responses: {
692
694
  200: {
693
- description,
695
+ description: summary,
694
696
  content: {
695
697
  'application/json': {
696
698
  schema: resolveResponse({
697
699
  schema,
698
700
  operation: info.operation,
701
+ customScalars,
699
702
  }),
700
703
  },
701
704
  },
@@ -747,26 +750,26 @@ function resolveParamSchema(type) {
747
750
  $ref: mapToRef(type.name.value),
748
751
  });
749
752
  }
750
- function resolveResponse({ schema, operation, }) {
753
+ function resolveResponse({ schema, operation, customScalars, }) {
751
754
  const operationType = operation.operation;
752
755
  const rootField = operation.selectionSet.selections[0];
753
756
  if (rootField.kind === graphql.Kind.FIELD) {
754
757
  if (operationType === 'query') {
755
758
  const queryType = schema.getQueryType();
756
759
  const field = queryType.getFields()[rootField.name.value];
757
- return resolveFieldType(field.type);
760
+ return resolveFieldType(field.type, { customScalars });
758
761
  }
759
762
  if (operationType === 'mutation') {
760
763
  const mutationType = schema.getMutationType();
761
764
  const field = mutationType.getFields()[rootField.name.value];
762
- return resolveFieldType(field.type);
765
+ return resolveFieldType(field.type, { customScalars });
763
766
  }
764
767
  }
765
768
  }
766
769
  function isInPath(url, param) {
767
770
  return url.indexOf(`{${param}}`) !== -1;
768
771
  }
769
- function resolveDescription(schema, operation) {
772
+ function resolveSummary(schema, operation) {
770
773
  const selection = operation.selectionSet.selections[0];
771
774
  const fieldName = selection.name.value;
772
775
  const typeDefinition = schema.getType(titleCase.titleCase(operation.operation));
@@ -787,13 +790,13 @@ function isObjectTypeDefinitionNode(node) {
787
790
  return node.kind === graphql.Kind.OBJECT_TYPE_DEFINITION;
788
791
  }
789
792
 
790
- function OpenAPI({ schema, info, servers, components, security, tags, }) {
793
+ function OpenAPI({ schema, info, servers, components, security, tags, customScalars = {}, }) {
791
794
  const types = schema.getTypeMap();
792
795
  const swagger = {
793
796
  openapi: '3.0.0',
794
797
  info,
795
798
  servers,
796
- tags,
799
+ tags: [],
797
800
  paths: {},
798
801
  components: {
799
802
  schemas: {},
@@ -803,7 +806,9 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
803
806
  const type = types[typeName];
804
807
  if ((graphql.isObjectType(type) || graphql.isInputObjectType(type)) &&
805
808
  !graphql.isIntrospectionType(type)) {
806
- swagger.components.schemas[typeName] = buildSchemaObjectFromType(type);
809
+ swagger.components.schemas[typeName] = buildSchemaObjectFromType(type, {
810
+ customScalars,
811
+ });
807
812
  }
808
813
  }
809
814
  if (components) {
@@ -812,6 +817,9 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
812
817
  if (security) {
813
818
  swagger.security = security;
814
819
  }
820
+ if (tags) {
821
+ swagger.tags = tags;
822
+ }
815
823
  return {
816
824
  addRoute(info, config) {
817
825
  const basePath = (config === null || config === void 0 ? void 0 : config.basePath) || '';
@@ -820,11 +828,15 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
820
828
  if (!swagger.paths[path]) {
821
829
  swagger.paths[path] = {};
822
830
  }
823
- swagger.paths[path][info.method.toLowerCase()] = buildPathFromOperation({
831
+ const pathsObj = swagger.paths[path];
832
+ pathsObj[info.method.toLowerCase()] = buildPathFromOperation({
824
833
  url: path,
825
834
  operation: info.document,
826
835
  schema,
827
836
  useRequestBody: ['POST', 'PUT', 'PATCH'].includes(info.method),
837
+ tags: info.tags || [],
838
+ description: info.description || '',
839
+ customScalars,
828
840
  });
829
841
  },
830
842
  get() {
package/index.mjs CHANGED
@@ -3,8 +3,7 @@ import { getOperationAST, Kind, isScalarType, isEqualType, GraphQLBoolean, isInp
3
3
  import { Router } from 'itty-router';
4
4
  import { buildOperationNodeForField } from '@graphql-tools/utils';
5
5
  import { paramCase } from 'param-case';
6
- import { v4 } from 'uuid';
7
- import { fetch, Response } from '@whatwg-node/fetch';
6
+ import { crypto, fetch, Response } from '@whatwg-node/fetch';
8
7
  import colors from 'ansi-colors';
9
8
  import { createServerAdapter } from '@whatwg-node/server';
10
9
  import { titleCase } from 'title-case';
@@ -108,20 +107,17 @@ class SubscriptionManager {
108
107
  }
109
108
  start(event, contextValue) {
110
109
  return __awaiter(this, void 0, void 0, function* () {
111
- const id = v4();
110
+ const id = crypto.randomUUID();
112
111
  const name = event.subscription;
113
112
  if (!this.operations.has(name)) {
114
113
  throw new Error(`Subscription '${name}' is not available`);
115
114
  }
116
- const { document, operationName, variables } = this.operations.get(name);
117
115
  logger.info(`[Subscription] Start ${id}`, event);
118
116
  const result = yield this.execute({
119
117
  id,
120
118
  name,
121
119
  url: event.url,
122
- document,
123
- operationName,
124
- variables,
120
+ variables: event.variables,
125
121
  contextValue,
126
122
  });
127
123
  if (typeof result !== 'undefined') {
@@ -160,9 +156,9 @@ class SubscriptionManager {
160
156
  }, contextValue);
161
157
  });
162
158
  }
163
- execute({ id, document, name, url, operationName, variables, contextValue, }) {
159
+ execute({ id, name, url, variables, contextValue, }) {
164
160
  return __awaiter(this, void 0, void 0, function* () {
165
- const variableNodes = this.operations.get(name).variables;
161
+ const { document, operationName, variables: variableNodes } = this.operations.get(name);
166
162
  const variableValues = variableNodes.reduce((values, variable) => {
167
163
  const value = parseVariable({
168
164
  value: variables[variable.variable.name.value],
@@ -172,7 +168,7 @@ class SubscriptionManager {
172
168
  if (typeof value === 'undefined') {
173
169
  return values;
174
170
  }
175
- return Object.assign(Object.assign({}, values), { [name]: value });
171
+ return Object.assign(Object.assign({}, values), { [variable.variable.name.value]: value });
176
172
  }, {});
177
173
  const execution = yield this.sofa.subscribe({
178
174
  schema: this.sofa.schema,
@@ -365,7 +361,7 @@ function createRouter(sofa) {
365
361
  return router;
366
362
  }
367
363
  function createQueryRoute({ sofa, router, fieldName, }) {
368
- var _a, _b, _c, _d;
364
+ var _a, _b, _c, _d, _e, _f;
369
365
  logger.debug(`[Router] Creating ${fieldName} query`);
370
366
  const queryType = sofa.schema.getQueryType();
371
367
  const operationNode = buildOperationNodeForField({
@@ -399,6 +395,8 @@ function createQueryRoute({ sofa, router, fieldName, }) {
399
395
  document: operation,
400
396
  path: route.path,
401
397
  method: route.method.toUpperCase(),
398
+ tags: (_e = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.tags) !== null && _e !== void 0 ? _e : [],
399
+ description: (_f = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.description) !== null && _f !== void 0 ? _f : '',
402
400
  };
403
401
  }
404
402
  function createMutationRoute({ sofa, router, fieldName, }) {
@@ -432,6 +430,8 @@ function createMutationRoute({ sofa, router, fieldName, }) {
432
430
  document: operation,
433
431
  path,
434
432
  method,
433
+ tags: (routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.tags) || [],
434
+ description: (routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.description) || '',
435
435
  };
436
436
  }
437
437
  function useHandler(config) {
@@ -613,7 +613,7 @@ function mapToRef(type) {
613
613
  return `#/components/schemas/${type}`;
614
614
  }
615
615
 
616
- function buildSchemaObjectFromType(type) {
616
+ function buildSchemaObjectFromType(type, opts) {
617
617
  const required = [];
618
618
  const properties = {};
619
619
  const fields = type.getFields();
@@ -622,28 +622,27 @@ function buildSchemaObjectFromType(type) {
622
622
  if (isNonNullType(field.type)) {
623
623
  required.push(field.name);
624
624
  }
625
- properties[fieldName] = resolveField(field);
625
+ properties[fieldName] = resolveField(field, opts);
626
626
  if (field.description) {
627
627
  properties[fieldName].description = field.description;
628
628
  }
629
629
  }
630
630
  return Object.assign(Object.assign(Object.assign({ type: 'object' }, (required.length ? { required } : {})), { properties }), (type.description ? { description: type.description } : {}));
631
631
  }
632
- function resolveField(field) {
633
- return resolveFieldType(field.type);
632
+ function resolveField(field, opts) {
633
+ return resolveFieldType(field.type, opts);
634
634
  }
635
635
  // array -> [type]
636
636
  // type -> $ref
637
637
  // scalar -> swagger primitive
638
- function resolveFieldType(type) {
639
- var _a, _b;
638
+ function resolveFieldType(type, opts) {
640
639
  if (isNonNullType(type)) {
641
- return resolveFieldType(type.ofType);
640
+ return resolveFieldType(type.ofType, opts);
642
641
  }
643
642
  if (isListType(type)) {
644
643
  return {
645
644
  type: 'array',
646
- items: resolveFieldType(type.ofType),
645
+ items: resolveFieldType(type.ofType, opts),
647
646
  };
648
647
  }
649
648
  if (isObjectType(type)) {
@@ -652,14 +651,15 @@ function resolveFieldType(type) {
652
651
  };
653
652
  }
654
653
  if (isScalarType(type)) {
655
- return (mapToPrimitive(type.name) || {
654
+ return (mapToPrimitive(type.name) ||
655
+ opts.customScalars[type.name] || {
656
656
  type: 'object',
657
657
  });
658
658
  }
659
659
  if (isEnumType(type)) {
660
660
  return {
661
661
  type: 'string',
662
- enum: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.map((value) => value.name.value),
662
+ enum: type.getValues().map((value) => value.name),
663
663
  };
664
664
  }
665
665
  return {
@@ -667,10 +667,12 @@ function resolveFieldType(type) {
667
667
  };
668
668
  }
669
669
 
670
- function buildPathFromOperation({ url, schema, operation, useRequestBody, }) {
670
+ function buildPathFromOperation({ url, schema, operation, useRequestBody, tags, description, customScalars, }) {
671
671
  const info = getOperationInfo(operation);
672
- const description = resolveDescription(schema, info.operation);
673
- return Object.assign(Object.assign({ operationId: info.name }, (useRequestBody
672
+ const summary = resolveSummary(schema, info.operation);
673
+ return Object.assign(Object.assign({ tags,
674
+ description,
675
+ summary, operationId: info.name }, (useRequestBody
674
676
  ? {
675
677
  requestBody: {
676
678
  content: {
@@ -684,12 +686,13 @@ function buildPathFromOperation({ url, schema, operation, useRequestBody, }) {
684
686
  parameters: resolveParameters(url, info.operation.variableDefinitions),
685
687
  })), { responses: {
686
688
  200: {
687
- description,
689
+ description: summary,
688
690
  content: {
689
691
  'application/json': {
690
692
  schema: resolveResponse({
691
693
  schema,
692
694
  operation: info.operation,
695
+ customScalars,
693
696
  }),
694
697
  },
695
698
  },
@@ -741,26 +744,26 @@ function resolveParamSchema(type) {
741
744
  $ref: mapToRef(type.name.value),
742
745
  });
743
746
  }
744
- function resolveResponse({ schema, operation, }) {
747
+ function resolveResponse({ schema, operation, customScalars, }) {
745
748
  const operationType = operation.operation;
746
749
  const rootField = operation.selectionSet.selections[0];
747
750
  if (rootField.kind === Kind.FIELD) {
748
751
  if (operationType === 'query') {
749
752
  const queryType = schema.getQueryType();
750
753
  const field = queryType.getFields()[rootField.name.value];
751
- return resolveFieldType(field.type);
754
+ return resolveFieldType(field.type, { customScalars });
752
755
  }
753
756
  if (operationType === 'mutation') {
754
757
  const mutationType = schema.getMutationType();
755
758
  const field = mutationType.getFields()[rootField.name.value];
756
- return resolveFieldType(field.type);
759
+ return resolveFieldType(field.type, { customScalars });
757
760
  }
758
761
  }
759
762
  }
760
763
  function isInPath(url, param) {
761
764
  return url.indexOf(`{${param}}`) !== -1;
762
765
  }
763
- function resolveDescription(schema, operation) {
766
+ function resolveSummary(schema, operation) {
764
767
  const selection = operation.selectionSet.selections[0];
765
768
  const fieldName = selection.name.value;
766
769
  const typeDefinition = schema.getType(titleCase(operation.operation));
@@ -781,13 +784,13 @@ function isObjectTypeDefinitionNode(node) {
781
784
  return node.kind === Kind.OBJECT_TYPE_DEFINITION;
782
785
  }
783
786
 
784
- function OpenAPI({ schema, info, servers, components, security, tags, }) {
787
+ function OpenAPI({ schema, info, servers, components, security, tags, customScalars = {}, }) {
785
788
  const types = schema.getTypeMap();
786
789
  const swagger = {
787
790
  openapi: '3.0.0',
788
791
  info,
789
792
  servers,
790
- tags,
793
+ tags: [],
791
794
  paths: {},
792
795
  components: {
793
796
  schemas: {},
@@ -797,7 +800,9 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
797
800
  const type = types[typeName];
798
801
  if ((isObjectType(type) || isInputObjectType(type)) &&
799
802
  !isIntrospectionType(type)) {
800
- swagger.components.schemas[typeName] = buildSchemaObjectFromType(type);
803
+ swagger.components.schemas[typeName] = buildSchemaObjectFromType(type, {
804
+ customScalars,
805
+ });
801
806
  }
802
807
  }
803
808
  if (components) {
@@ -806,6 +811,9 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
806
811
  if (security) {
807
812
  swagger.security = security;
808
813
  }
814
+ if (tags) {
815
+ swagger.tags = tags;
816
+ }
809
817
  return {
810
818
  addRoute(info, config) {
811
819
  const basePath = (config === null || config === void 0 ? void 0 : config.basePath) || '';
@@ -814,11 +822,15 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
814
822
  if (!swagger.paths[path]) {
815
823
  swagger.paths[path] = {};
816
824
  }
817
- swagger.paths[path][info.method.toLowerCase()] = buildPathFromOperation({
825
+ const pathsObj = swagger.paths[path];
826
+ pathsObj[info.method.toLowerCase()] = buildPathFromOperation({
818
827
  url: path,
819
828
  operation: info.document,
820
829
  schema,
821
830
  useRequestBody: ['POST', 'PUT', 'PATCH'].includes(info.method),
831
+ tags: info.tags || [],
832
+ description: info.description || '',
833
+ customScalars,
822
834
  });
823
835
  },
824
836
  get() {
@@ -1,15 +1,26 @@
1
1
  import { GraphQLSchema } from 'graphql';
2
2
  import { RouteInfo } from '../types';
3
- export declare function OpenAPI({ schema, info, servers, components, security, tags, }: {
3
+ import { OpenAPIV3 } from 'openapi-types';
4
+ export declare function OpenAPI({ schema, info, servers, components, security, tags, customScalars, }: {
4
5
  schema: GraphQLSchema;
5
- info: Record<string, any>;
6
- servers?: Record<string, any>[];
6
+ info: OpenAPIV3.InfoObject;
7
+ servers?: OpenAPIV3.ServerObject[];
7
8
  components?: Record<string, any>;
8
- security?: Record<string, any>[];
9
- tags?: Record<string, any>[];
9
+ security?: OpenAPIV3.SecurityRequirementObject[];
10
+ tags?: OpenAPIV3.TagObject[];
11
+ /**
12
+ * Override mapping of custom scalars to OpenAPI
13
+ * @example
14
+ * ```js
15
+ * {
16
+ * Date: { type: "string", format: "date" }
17
+ * }
18
+ * ```
19
+ */
20
+ customScalars?: Record<string, any>;
10
21
  }): {
11
22
  addRoute(info: RouteInfo, config?: {
12
23
  basePath?: string;
13
24
  }): void;
14
- get(): any;
25
+ get(): OpenAPIV3.Document<{}>;
15
26
  };
@@ -1,7 +1,11 @@
1
1
  import { DocumentNode, GraphQLSchema } from 'graphql';
2
- export declare function buildPathFromOperation({ url, schema, operation, useRequestBody, }: {
2
+ import { OpenAPIV3 } from 'openapi-types';
3
+ export declare function buildPathFromOperation({ url, schema, operation, useRequestBody, tags, description, customScalars, }: {
3
4
  url: string;
4
5
  schema: GraphQLSchema;
5
6
  operation: DocumentNode;
6
7
  useRequestBody: boolean;
7
- }): any;
8
+ tags?: string[];
9
+ description?: string;
10
+ customScalars: Record<string, any>;
11
+ }): OpenAPIV3.OperationObject;
@@ -1,3 +1,7 @@
1
1
  import { GraphQLObjectType, GraphQLInputObjectType, GraphQLType } from 'graphql';
2
- export declare function buildSchemaObjectFromType(type: GraphQLObjectType | GraphQLInputObjectType): any;
3
- export declare function resolveFieldType(type: GraphQLType): any;
2
+ export declare function buildSchemaObjectFromType(type: GraphQLObjectType | GraphQLInputObjectType, opts: {
3
+ customScalars: Record<string, any>;
4
+ }): any;
5
+ export declare function resolveFieldType(type: GraphQLType, opts: {
6
+ customScalars: Record<string, any>;
7
+ }): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sofa-api",
3
- "version": "0.12.0-alpha.0",
3
+ "version": "0.12.0",
4
4
  "description": "Create REST APIs with GraphQL",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -8,14 +8,14 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@graphql-tools/utils": "8.12.0",
11
+ "ansi-colors": "4.1.3",
11
12
  "@whatwg-node/fetch": "^0.4.3",
12
13
  "@whatwg-node/server": "^0.4.1",
13
- "ansi-colors": "4.1.3",
14
14
  "itty-router": "^2.6.1",
15
+ "openapi-types": "12.0.2",
15
16
  "param-case": "3.0.4",
16
17
  "title-case": "3.0.3",
17
- "tslib": "2.4.0",
18
- "uuid": "8.3.2"
18
+ "tslib": "2.4.0"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -50,4 +50,4 @@
50
50
  },
51
51
  "./package.json": "./package.json"
52
52
  }
53
- }
53
+ }
package/sofa.d.ts CHANGED
@@ -5,6 +5,8 @@ interface RouteConfig {
5
5
  method?: Method;
6
6
  path?: string;
7
7
  responseStatus?: number;
8
+ tags?: string[];
9
+ description?: string;
8
10
  }
9
11
  export interface Route {
10
12
  method: Method;
package/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface RouteInfo {
6
6
  document: DocumentNode;
7
7
  path: string;
8
8
  method: Method;
9
+ tags?: string[];
10
+ description?: string;
9
11
  }
10
12
  export declare type OnRoute = (info: RouteInfo) => void;
11
13
  export declare type ContextFn = (init: {
@@ -1,325 +0,0 @@
1
- declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
2
- export declare namespace OpenAPI {
3
- export interface Contact {
4
- /**
5
- * The identifying name of the contact person/organization.
6
- */
7
- name?: string;
8
- /**
9
- * The URL pointing to the contact information. MUST be in the format of a URL.
10
- */
11
- url?: string;
12
- /**
13
- * The email address of the contact person/organization. MUST be in the format of an email address.
14
- */
15
- email?: string;
16
- }
17
- export interface License {
18
- /**
19
- * The license name used for the API.
20
- */
21
- name: string;
22
- /**
23
- * A URL to the license used for the API. MUST be in the format of a URL.
24
- */
25
- url?: string;
26
- }
27
- export interface Info {
28
- /**
29
- * The title of the application.
30
- */
31
- title: string;
32
- /**
33
- * A short description of the application. CommonMark syntax MAY be used for rich text representation.
34
- */
35
- description?: string;
36
- /**
37
- * A URL to the Terms of Service for the API. MUST be in the format of a URL.
38
- */
39
- termsOfService?: string;
40
- /**
41
- * The contact information for the exposed API.
42
- */
43
- contact?: Contact;
44
- /**
45
- * The license information for the exposed API.
46
- */
47
- license?: License;
48
- /**
49
- * The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).
50
- */
51
- version: string;
52
- }
53
- export interface Reference {
54
- /**
55
- * The reference string.
56
- */
57
- $ref: string;
58
- }
59
- export interface Parameter {
60
- /**
61
- * The name of the parameter. Parameter names are case sensitive.
62
- * - If in is "path", the name field MUST correspond to the associated path segment from the path field in the Paths Object. See Path Templating for further information.
63
- * - If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition SHALL be ignored.
64
- * - For all other cases, the name corresponds to the parameter name used by the in property
65
- */
66
- name: string;
67
- /**
68
- * The location of the parameter. Possible values are "query", "header", "path" or "cookie".
69
- */
70
- in: 'query' | 'header' | 'path' | 'cookie';
71
- /**
72
- * A brief description of the parameter. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
73
- */
74
- description?: string;
75
- /**
76
- * Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.
77
- */
78
- required: boolean;
79
- /**
80
- * Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false.
81
- */
82
- deprecated?: boolean;
83
- /**
84
- * Sets the ability to pass empty-valued parameters. This is valid only for query parameters and allows sending a parameter with an empty value. Default value is false. If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision.
85
- */
86
- allowEmptyValue?: boolean;
87
- schema?: Reference | Schema;
88
- }
89
- export interface RequestBody {
90
- /**
91
- * A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
92
- */
93
- description?: string;
94
- /**
95
- * The content of the request body. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
96
- */
97
- content: Array<string>;
98
- /**
99
- * Determines if the request body is required in the request. Defaults to false.
100
- */
101
- required?: boolean;
102
- }
103
- export type Header = Omit<Parameter, 'name' | 'in'>;
104
- export interface Link {
105
- /**
106
- * A relative or absolute reference to an OAS operation. This field is mutually exclusive of the operationId field, and MUST point to an Operation Object. Relative operationRef values MAY be used to locate an existing Operation Object in the OpenAPI definition.
107
- */
108
- operationRef?: string;
109
- /**
110
- * The name of an existing, resolvable OAS operation, as defined with a unique operationId. This field is mutually exclusive of the operationRef field.
111
- */
112
- operationId?: string;
113
- /**
114
- * A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation. The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use the same parameter name in different locations (e.g. path.id).
115
- */
116
- parameters?: Record<string, any>;
117
- /**
118
- * A literal value or {expression} to use as a request body when calling the target operation.
119
- */
120
- requestBody?: any;
121
- /**
122
- * A description of the link. CommonMark syntax MAY be used for rich text representation.
123
- */
124
- description?: string;
125
- }
126
- interface Response {
127
- /**
128
- * A short description of the response. CommonMark syntax MAY be used for rich text representation.
129
- */
130
- description: string;
131
- headers?: {
132
- [header: string]: Header | Reference;
133
- };
134
- links?: {
135
- [link: string]: Link | Reference;
136
- };
137
- }
138
- interface Responses {
139
- default: Response | Reference;
140
- [httpStatusCode: number]: Response | Reference;
141
- }
142
- export interface Operation {
143
- /**
144
- * A list of tags for API documentation control.
145
- * Tags can be used for logical grouping of operations by resources or any other qualifier.
146
- */
147
- tags?: string[];
148
- /**
149
- * A short summary of what the operation does.
150
- */
151
- summary?: string;
152
- /**
153
- * A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation.
154
- */
155
- description?: string;
156
- /**
157
- * Additional external documentation for this operation.
158
- */
159
- externalDocs?: {
160
- /**
161
- * The URL for the target documentation. Value MUST be in the format of a URL.
162
- */
163
- url: string;
164
- /**
165
- * A short description of the target documentation. CommonMark syntax MAY be used for rich text representation.
166
- */
167
- description?: string;
168
- };
169
- /**
170
- * Unique string used to identify the operation.
171
- * The id MUST be unique among all operations described in the API.
172
- * The operationId value is case-sensitive.
173
- * Tools and libraries MAY use the operationId to uniquely identify an operation,
174
- * therefore, it is RECOMMENDED to follow common programming naming conventions.
175
- */
176
- operationId?: string;
177
- /**
178
- * A list of parameters that are applicable for this operation.
179
- * If a parameter is already defined at the Path Item, the new definition will override it but can never remove it.
180
- * The list MUST NOT include duplicated parameters.
181
- * A unique parameter is defined by a combination of a name and location.
182
- * The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters.
183
- */
184
- parameters?: Array<Parameter | Reference>;
185
- /**
186
- * The request body applicable for this operation. The requestBody is only supported in HTTP methods where the HTTP 1.1 specification RFC7231 has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
187
- */
188
- requestBody?: RequestBody | Reference;
189
- /**
190
- * The list of possible responses as they are returned from executing this operation.
191
- */
192
- responses?: Responses;
193
- /**
194
- * Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is false.
195
- */
196
- deprecated?: boolean;
197
- }
198
- export interface PathItem {
199
- /**
200
- * Allows for an external definition of this path item.
201
- * The referenced structure MUST be in the format of a Path Item Object.
202
- * If there are conflicts between the referenced definition and this Path Item's definition, the behavior is undefined.
203
- */
204
- $ref?: string;
205
- /**
206
- * An optional, string summary, intended to apply to all operations in this path.
207
- */
208
- summary?: string;
209
- /**
210
- * An optional, string description, intended to apply to all operations in this path.
211
- * CommonMark syntax MAY be used for rich text representation.
212
- */
213
- description?: string;
214
- /**
215
- * A definition of a GET operation on this path.
216
- */
217
- get?: Operation;
218
- /**
219
- * A definition of a PUT operation on this path.
220
- */
221
- put?: Operation;
222
- /**
223
- * A definition of a POST operation on this path.
224
- */
225
- post?: Operation;
226
- /**
227
- * A definition of a DELETE operation on this path.
228
- */
229
- delete?: Operation;
230
- /**
231
- * A definition of a OPTIONS operation on this path.
232
- */
233
- options?: Operation;
234
- /**
235
- * A definition of a HEAD operation on this path.
236
- */
237
- head?: Operation;
238
- /**
239
- * A definition of a PATCH operation on this path.
240
- */
241
- patch?: Operation;
242
- /**
243
- * A definition of a TRACE operation on this path.
244
- */
245
- trace?: Operation;
246
- /**
247
- * A list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at the operation level, but cannot be removed there. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters.
248
- */
249
- parameters?: Array<Parameter | Reference>;
250
- }
251
- export interface XML {
252
- /**
253
- * Replaces the name of the element/attribute used for the described schema property. When defined within items, it will affect the name of the individual XML elements within the list. When defined alongside type being array (outside the items), it will affect the wrapping element and only if wrapped is true. If wrapped is false, it will be ignored.
254
- */
255
- name?: string;
256
- /**
257
- * The URI of the namespace definition. Value MUST be in the form of an absolute URI.
258
- */
259
- namespace?: string;
260
- /**
261
- * The prefix to be used for the name.
262
- */
263
- prefix?: string;
264
- /**
265
- * Declares whether the property definition translates to an attribute instead of an element. Default value is false.
266
- */
267
- attribute?: boolean;
268
- /**
269
- * MAY be used only for an array definition. Signifies whether the array is wrapped (for example, <books><book/><book/></books>) or unwrapped (<book/><book/>). Default value is false. The definition takes effect only when defined alongside type being array (outside the items).
270
- */
271
- wrapped?: boolean;
272
- }
273
- export interface Schema {
274
- /**
275
- * Allows sending a null value for the defined schema. Default value is false.
276
- */
277
- nullable?: boolean;
278
- /**
279
- * Relevant only for Schema "properties" definitions. Declares the property as "read only". This means that it MAY be sent as part of a response but SHOULD NOT be sent as part of the request. If the property is marked as readOnly being true and is in the required list, the required will take effect on the response only. A property MUST NOT be marked as both readOnly and writeOnly being true. Default value is false.
280
- */
281
- readOnly?: boolean;
282
- /**
283
- * Relevant only for Schema "properties" definitions. Declares the property as "write only". Therefore, it MAY be sent as part of a request but SHOULD NOT be sent as part of the response. If the property is marked as writeOnly being true and is in the required list, the required will take effect on the request only. A property MUST NOT be marked as both readOnly and writeOnly being true. Default value is false.
284
- */
285
- writeOnly?: boolean;
286
- /**
287
- * This MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property.
288
- */
289
- xml?: XML;
290
- /**
291
- * Specifies that a schema is deprecated and SHOULD be transitioned out of usage. Default value is false.
292
- */
293
- deprecated?: boolean;
294
- }
295
- export interface Components {
296
- /**
297
- * An object to hold reusable Schema Objects.
298
- */
299
- schemas?: Record<string, Schema | Reference>;
300
- }
301
- export interface OpenAPI {
302
- /**
303
- * This string MUST be the semantic version number of the OpenAPI Specification version that the OpenAPI document uses.
304
- * The openapi field SHOULD be used by tooling specifications and clients to interpret the OpenAPI document.
305
- * This is not related to the API info.version string.
306
- */
307
- openapi: string;
308
- /**
309
- * Provides metadata about the API. The metadata MAY be used by tooling as required.
310
- */
311
- info: Info;
312
- /**
313
- * The available paths and operations for the API.
314
- */
315
- paths: {
316
- [path: string]: PathItem;
317
- };
318
- /**
319
- * An element to hold various schemas for the specification.
320
- */
321
- components?: Components;
322
- }
323
- export {};
324
- }
325
- export {};