sofa-api 0.11.0 → 0.11.2-alpha.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/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Change log
2
+
3
+ ### vNEXT
4
+
5
+ ### v0.7.0
6
+
7
+ - feat(swagger): custom components and security for OpenAPI [PR #296](https://github.com/Urigo/SOFA/pull/296)
8
+
9
+ ### v0.6.1
10
+
11
+ - fix(swagger): broken type of ID scalar in generated swagger document [PR #280](https://github.com/Urigo/SOFA/pull/280)
12
+ - fix: do not resolve nil variables [PR #193](https://github.com/Urigo/SOFA/pull/193)
13
+ - fix: use Kind from graphql instead of strings [PR #192](https://github.com/Urigo/SOFA/pull/192)
14
+ - fix: replace request package with Axios [PR #194](https://github.com/Urigo/SOFA/pull/194)
15
+
16
+ ### v0.6.0
17
+
18
+ - fix(swagger): fix descriptions [PR #178](https://github.com/Urigo/SOFA/pull/178)
19
+ - feat: support GraphQL Interfaces [PR #167](https://github.com/Urigo/SOFA/pull/167)
20
+
21
+ ### v0.5.1
22
+
23
+ - fix: do not skip falsy values (only null and undefined) [PR #134](https://github.com/Urigo/SOFA/pull/134)
24
+
25
+ ### v0.5.0
26
+
27
+ - feat(swagger): add description [PR #107](https://github.com/Urigo/SOFA/pull/107)
28
+ - feat(swagger): add `ID` scalar definition [PR #107](https://github.com/Urigo/SOFA/pull/107)
29
+ - fix(swagger): use `requestBody` or `parameters` not both [PR #107](https://github.com/Urigo/SOFA/pull/107)
30
+ - fix(swagger): generate valid YAML structure for nested objects [PR #107](https://github.com/Urigo/SOFA/pull/107)
31
+ - fix(swagger): avoid empty `required` array [PR #107](https://github.com/Urigo/SOFA/pull/107)
32
+
33
+ ### v0.4.0
34
+
35
+ - feat: add error handler [PR #44](https://github.com/Urigo/SOFA/pull/44) [PR #45](https://github.com/Urigo/SOFA/pull/45)
36
+ - feat: allow to customize endpoint's HTTP Method [PR #46](https://github.com/Urigo/SOFA/pull/46)
37
+ - feat: add InputTypeObjects to OpenAPI generation [PR #34](https://github.com/Urigo/SOFA/pull/34) - [@hajnalben](https://github.com/hajnalben)
38
+
39
+ ### v0.3.0
40
+
41
+ - fix: parse InputTypeObject with JSON.parse [PR #30](https://github.com/Urigo/SOFA/pull/30)
42
+ - feat: custom `depthLimit` (circular references) [PR #29](https://github.com/Urigo/SOFA/pull/29)
43
+
44
+ ### v0.2.3
45
+
46
+ - fix: prevent circular references [PR #23](https://github.com/Urigo/SOFA/pull/23)
47
+
48
+ ### v0.2.2
49
+
50
+ We didn't track changes before this version.
package/README.md CHANGED
@@ -20,47 +20,14 @@ Here's complete example with no dependency on frameworks, but also integratable
20
20
  ```js
21
21
  import http from 'http';
22
22
  import getStream from 'get-stream';
23
- import { createSofaRouter } from 'sofa-api';
24
-
25
- const invokeSofa = createSofaRouter({
26
- basePath: '/api',
27
- schema,
28
- });
23
+ import { useSofa } from 'sofa-api';
29
24
 
30
- const server = http.createServer(async (req, res) => {
31
- try {
32
- const response = await invokeSofa({
33
- method: req.method,
34
- url: req.url,
35
- body: JSON.parse(await getStream(req)),
36
- contextValue: {
37
- req,
38
- },
39
- });
40
- if (response) {
41
- const headers = {
42
- 'Content-Type': 'application/json',
43
- };
44
- if (response.statusMessage) {
45
- res.writeHead(response.status, response.statusMessage, headers);
46
- } else {
47
- res.writeHead(response.status, headers);
48
- }
49
- if (response.type === 'result') {
50
- res.end(JSON.stringify(response.body));
51
- }
52
- if (response.type === 'error') {
53
- res.end(JSON.stringify(response.error));
54
- }
55
- } else {
56
- res.writeHead(404);
57
- res.end();
58
- }
59
- } catch (error) {
60
- res.writeHead(500);
61
- res.end(JSON.stringify(error));
62
- }
63
- });
25
+ const server = http.createServer(
26
+ useSofa({
27
+ basePath: '/api',
28
+ schema,
29
+ })
30
+ );
64
31
  ```
65
32
 
66
33
  Another example with builtin express-like frameworks support
@@ -302,6 +269,7 @@ Thanks to GraphQL's Type System Sofa is able to generate OpenAPI (Swagger) defin
302
269
 
303
270
  ```ts
304
271
  import { useSofa, OpenAPI } from 'sofa-api';
272
+ import { writeFileSync } from 'fs';
305
273
 
306
274
  const openApi = OpenAPI({
307
275
  schema,
@@ -325,13 +293,14 @@ app.use(
325
293
  );
326
294
 
327
295
  // writes every recorder route
328
- openApi.save('./swagger.yml');
296
+ writeFileSync('./swagger.json', JSON.stringify(openApi.get(), null, 2));
329
297
  ```
330
298
 
331
299
  OpenAPI (Swagger) with Bearer Authentication
332
300
 
333
301
  ```ts
334
302
  import { useSofa, OpenAPI } from 'sofa-api';
303
+ import { writeFileSync } from 'fs';
335
304
 
336
305
  const openApi = OpenAPI({
337
306
  schema,
@@ -369,7 +338,7 @@ app.use(
369
338
  );
370
339
 
371
340
  // writes every recorder route
372
- openApi.save('./swagger.yml');
341
+ writeFileSync('./swagger.json', JSON.stringify(openApi.get(), null, 2));
373
342
  ```
374
343
 
375
344
  ## License
package/bob.config.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = {};
package/bump.js ADDED
@@ -0,0 +1,18 @@
1
+ const semver = require('semver');
2
+ const packageJson = require('./package.json');
3
+ const fs = require('fs');
4
+ const cp = require('child_process');
5
+
6
+ const gitHash = cp
7
+ .spawnSync('git', ['rev-parse', '--short', 'HEAD'])
8
+ .stdout.toString()
9
+ .trim();
10
+ const alphaVersion = semver.inc(
11
+ packageJson.version,
12
+ 'prerelease',
13
+ true,
14
+ gitHash
15
+ );
16
+ packageJson.version = alphaVersion;
17
+
18
+ fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2));
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Uri Goldshtein
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
File without changes
File without changes
@@ -0,0 +1,16 @@
1
+ /// <reference types="node" />
2
+ import * as http from 'http';
3
+ import type { ContextValue } from './types';
4
+ import type { SofaConfig } from './sofa';
5
+ import { ServerAdapter } from '@whatwg-node/server';
6
+ export { OpenAPI } from './open-api';
7
+ export declare type ContextFn = (init: {
8
+ req: any;
9
+ res: any;
10
+ }) => ContextValue;
11
+ export declare function isContextFn(context: any): context is ContextFn;
12
+ interface SofaMiddlewareConfig extends SofaConfig {
13
+ context?: ContextValue | ContextFn;
14
+ }
15
+ export declare function useSofa({ context, ...config }: SofaMiddlewareConfig): ServerAdapter<http.IncomingMessage, http.ServerResponse>;
16
+ export declare function createSofaRouter(config: SofaConfig): import("itty-router").Router<import("itty-router").Request, {}>;
@@ -6,12 +6,13 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
6
6
 
7
7
  const tslib = require('tslib');
8
8
  const graphql = require('graphql');
9
- const Trouter = _interopDefault(require('trouter'));
9
+ const ittyRouter = require('itty-router');
10
10
  const utils = require('@graphql-tools/utils');
11
11
  const paramCase = require('param-case');
12
12
  const uuid = require('uuid');
13
- const axios = _interopDefault(require('axios'));
14
- const colors = require('ansi-colors');
13
+ const fetch = require('@whatwg-node/fetch');
14
+ const colors = _interopDefault(require('ansi-colors'));
15
+ const server = require('@whatwg-node/server');
15
16
  const jsYaml = require('js-yaml');
16
17
  const fs = require('fs');
17
18
  const titleCase = require('title-case');
@@ -236,7 +237,14 @@ class SubscriptionManager {
236
237
  }
237
238
  const { url } = this.clients.get(id);
238
239
  logger.info(`[Subscription] Trigger ${id}`);
239
- yield axios.post(url, result);
240
+ const response = yield fetch.fetch(url, {
241
+ method: 'POST',
242
+ body: JSON.stringify(result),
243
+ headers: {
244
+ 'Content-Type': 'application/json',
245
+ }
246
+ });
247
+ yield response.text();
240
248
  });
241
249
  }
242
250
  buildOperations() {
@@ -270,7 +278,9 @@ class SubscriptionManager {
270
278
 
271
279
  function createRouter(sofa) {
272
280
  logger.debug('[Sofa] Creating router');
273
- const router = new Trouter();
281
+ const router = ittyRouter.Router({
282
+ base: sofa.basePath,
283
+ });
274
284
  const queryType = sofa.schema.getQueryType();
275
285
  const mutationType = sofa.schema.getMutationType();
276
286
  const subscriptionManager = new SubscriptionManager(sofa);
@@ -290,92 +300,75 @@ function createRouter(sofa) {
290
300
  }
291
301
  });
292
302
  }
293
- router.post('/webhook', ({ body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
294
- const { subscription, variables, url } = body;
303
+ router.post('/webhook', (request, contextValue) => tslib.__awaiter(this, void 0, void 0, function* () {
304
+ const { subscription, variables, url } = yield request.json();
295
305
  try {
296
306
  const result = yield subscriptionManager.start({
297
307
  subscription,
298
308
  variables,
299
309
  url,
300
310
  }, contextValue);
301
- return {
302
- type: 'result',
311
+ return new fetch.Response(JSON.stringify(result), {
303
312
  status: 200,
304
- statusMessage: 'OK',
305
- body: result,
306
- };
313
+ statusText: 'OK',
314
+ headers: {
315
+ 'Content-Type': 'application/json',
316
+ },
317
+ });
307
318
  }
308
319
  catch (error) {
309
- return {
310
- type: 'error',
320
+ return new fetch.Response(JSON.stringify(error), {
311
321
  status: 500,
312
- statusMessage: 'Subscription failed',
313
- error,
314
- };
322
+ statusText: 'Subscription failed',
323
+ });
315
324
  }
316
325
  }));
317
- router.post('/webhook/:id', ({ body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
318
- const id = params.id;
326
+ router.post('/webhook/:id', (request, contextValue) => tslib.__awaiter(this, void 0, void 0, function* () {
327
+ var _a;
328
+ const id = (_a = request.params) === null || _a === void 0 ? void 0 : _a.id;
329
+ const body = yield request.json();
319
330
  const variables = body.variables;
320
331
  try {
321
332
  const result = yield subscriptionManager.update({
322
333
  id,
323
334
  variables,
324
335
  }, contextValue);
325
- return {
326
- type: 'result',
336
+ return new fetch.Response(JSON.stringify(result), {
327
337
  status: 200,
328
- statusMessage: 'OK',
329
- body: result,
330
- };
338
+ statusText: 'OK',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ },
342
+ });
331
343
  }
332
344
  catch (error) {
333
- return {
334
- type: 'error',
345
+ return new fetch.Response(JSON.stringify(error), {
335
346
  status: 500,
336
- statusMessage: 'Subscription failed to update',
337
- error,
338
- };
347
+ statusText: 'Subscription failed to update',
348
+ });
339
349
  }
340
350
  }));
341
- router.delete('/webhook/:id', ({ params }) => tslib.__awaiter(this, void 0, void 0, function* () {
342
- const id = params.id;
351
+ router.delete('/webhook/:id', (request) => tslib.__awaiter(this, void 0, void 0, function* () {
352
+ var _b;
353
+ const id = (_b = request.params) === null || _b === void 0 ? void 0 : _b.id;
343
354
  try {
344
355
  const result = yield subscriptionManager.stop(id);
345
- return {
346
- type: 'result',
356
+ return new fetch.Response(JSON.stringify(result), {
347
357
  status: 200,
348
- statusMessage: 'OK',
349
- body: result,
350
- };
358
+ statusText: 'OK',
359
+ headers: {
360
+ 'Content-Type': 'application/json',
361
+ },
362
+ });
351
363
  }
352
364
  catch (error) {
353
- return {
354
- type: 'error',
365
+ return new fetch.Response(JSON.stringify(error), {
355
366
  status: 500,
356
- statusMessage: 'Subscription failed to stop',
357
- error,
358
- };
359
- }
360
- }));
361
- return ({ method, url, body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
362
- if (!url.startsWith(sofa.basePath)) {
363
- return null;
364
- }
365
- // trim base path and search
366
- const [slicedUrl] = url.slice(sofa.basePath.length).split('?');
367
- const trouterMethod = method.toUpperCase();
368
- const obj = router.find(trouterMethod, slicedUrl);
369
- for (const handler of obj.handlers) {
370
- return yield handler({
371
- url,
372
- body,
373
- params: obj.params,
374
- contextValue,
367
+ statusText: 'Subscription failed to stop',
375
368
  });
376
369
  }
377
- return null;
378
- });
370
+ }));
371
+ return router;
379
372
  }
380
373
  function createQueryRoute({ sofa, router, fieldName, }) {
381
374
  var _a, _b, _c, _d;
@@ -406,7 +399,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {
406
399
  path: (_c = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.path) !== null && _c !== void 0 ? _c : getPath(fieldName, isSingle && hasIdArgument),
407
400
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
408
401
  };
409
- router[route.method.toLocaleLowerCase()](route.path, useHandler({ info, route, fieldName, sofa, operation }));
402
+ router[route.method](route.path, useHandler({ info, route, fieldName, sofa, operation }));
410
403
  logger.debug(`[Router] ${fieldName} query available at ${route.method} ${route.path}`);
411
404
  return {
412
405
  document: operation,
@@ -439,7 +432,7 @@ function createMutationRoute({ sofa, router, fieldName, }) {
439
432
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
440
433
  };
441
434
  const { method, path } = route;
442
- router[method.toLowerCase()](path, useHandler({ info, route, fieldName, sofa, operation }));
435
+ router[method](path, useHandler({ info, route, fieldName, sofa, operation }));
443
436
  logger.debug(`[Router] ${fieldName} mutation available at ${method} ${path}`);
444
437
  return {
445
438
  document: operation,
@@ -450,11 +443,24 @@ function createMutationRoute({ sofa, router, fieldName, }) {
450
443
  function useHandler(config) {
451
444
  const { sofa, operation, fieldName } = config;
452
445
  const info = config.info;
453
- return ({ url, body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
446
+ return (request, contextValue) => tslib.__awaiter(this, void 0, void 0, function* () {
447
+ var _a;
448
+ let body = {};
449
+ if (request.body != null) {
450
+ const strBody = yield request.text();
451
+ if (strBody) {
452
+ body = JSON.parse(strBody);
453
+ }
454
+ }
454
455
  const variableValues = info.variables.reduce((variables, variable) => {
455
456
  const name = variable.variable.name.value;
456
457
  const value = parseVariable({
457
- value: pickParam({ url, body, params, name }),
458
+ value: pickParam({
459
+ url: request.url,
460
+ body,
461
+ params: request.params || {},
462
+ name,
463
+ }),
458
464
  variable,
459
465
  schema: sofa.schema,
460
466
  });
@@ -472,27 +478,26 @@ function useHandler(config) {
472
478
  });
473
479
  if (result.errors) {
474
480
  const defaultErrorHandler = (errors) => {
475
- return {
476
- type: 'error',
481
+ return new fetch.Response(errors[0], {
477
482
  status: 500,
478
- error: errors[0],
479
- };
483
+ });
480
484
  };
481
485
  const errorHandler = sofa.errorHandler || defaultErrorHandler;
482
486
  return errorHandler(result.errors);
483
487
  }
484
- return {
485
- type: 'result',
488
+ return new fetch.Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
486
489
  status: config.route.responseStatus,
487
- body: result.data && result.data[fieldName],
488
- };
490
+ headers: {
491
+ 'Content-Type': 'application/json',
492
+ },
493
+ });
489
494
  });
490
495
  }
491
496
  function getPath(fieldName, hasId = false) {
492
497
  return `/${convertName(fieldName)}${hasId ? '/:id' : ''}`;
493
498
  }
494
499
  function pickParam({ name, url, params, body, }) {
495
- if (params && params.hasOwnProperty(name)) {
500
+ if (name in params) {
496
501
  return params[name];
497
502
  }
498
503
  const searchParams = new URLSearchParams(url.split('?')[1]);
@@ -559,24 +564,8 @@ function extractsModels(schema) {
559
564
  }
560
565
  // it's dumb but let's leave it for now
561
566
  function isArrayOf(type, expected) {
562
- if (isOptionalList(type)) {
563
- return true;
564
- }
565
- if (graphql.isNonNullType(type) && isOptionalList(type.ofType)) {
566
- return true;
567
- }
568
- function isOptionalList(list) {
569
- if (graphql.isListType(list)) {
570
- if (list.ofType.name === expected.name) {
571
- return true;
572
- }
573
- if (graphql.isNonNullType(list.ofType) &&
574
- list.ofType.ofType.name === expected.name) {
575
- return true;
576
- }
577
- }
578
- }
579
- return false;
567
+ const typeNameInSdl = type.toString();
568
+ return (typeNameInSdl.includes('[') && typeNameInSdl.includes(expected.toString()));
580
569
  }
581
570
  function hasID(type) {
582
571
  return graphql.isObjectType(type) && !!type.getFields().id;
@@ -849,50 +838,10 @@ function isContextFn(context) {
849
838
  return typeof context === 'function';
850
839
  }
851
840
  function useSofa(_a) {
852
- var { context } = _a, config = tslib.__rest(_a, ["context"]);
853
- const invokeSofa = createSofaRouter(config);
854
- return (req, res, next) => tslib.__awaiter(this, void 0, void 0, function* () {
855
- var _b;
856
- try {
857
- let contextValue = { req };
858
- if (context) {
859
- if (typeof context === 'function') {
860
- contextValue = yield context({ req, res });
861
- }
862
- else {
863
- contextValue = context;
864
- }
865
- }
866
- const response = yield invokeSofa({
867
- method: req.method,
868
- url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
869
- body: req.body,
870
- contextValue,
871
- });
872
- if (response == null) {
873
- next();
874
- }
875
- else {
876
- const headers = {
877
- 'Content-Type': 'application/json',
878
- };
879
- if (response.statusMessage) {
880
- res.writeHead(response.status, response.statusMessage, headers);
881
- }
882
- else {
883
- res.writeHead(response.status, headers);
884
- }
885
- if (response.type === 'result') {
886
- res.end(JSON.stringify(response.body));
887
- }
888
- if (response.type === 'error') {
889
- res.end(JSON.stringify(response.error));
890
- }
891
- }
892
- }
893
- catch (error) {
894
- next(error);
895
- }
841
+ var config = tslib.__rest(_a, ["context"]);
842
+ const sofaRouter = createSofaRouter(config);
843
+ return server.createServerAdapter({
844
+ handleRequest: sofaRouter.handle,
896
845
  });
897
846
  }
898
847
  function createSofaRouter(config) {
@@ -1,11 +1,12 @@
1
1
  import { __awaiter, __asyncValues, __rest } from 'tslib';
2
2
  import { getOperationAST, Kind, isScalarType, isEqualType, GraphQLBoolean, isInputObjectType, subscribe, isObjectType, isNonNullType, print, graphql, getNamedType, isListType, isEnumType, parse, printType, isIntrospectionType } from 'graphql';
3
- import Trouter from 'trouter';
3
+ import { Router } from 'itty-router';
4
4
  import { buildOperationNodeForField } from '@graphql-tools/utils';
5
5
  import { paramCase } from 'param-case';
6
6
  import { v4 } from 'uuid';
7
- import axios from 'axios';
8
- import { red, yellow, green, blue } from 'ansi-colors';
7
+ import { fetch, Response } from '@whatwg-node/fetch';
8
+ import colors from 'ansi-colors';
9
+ import { createServerAdapter } from '@whatwg-node/server';
9
10
  import { dump } from 'js-yaml';
10
11
  import { writeFileSync } from 'fs';
11
12
  import { titleCase } from 'title-case';
@@ -84,16 +85,16 @@ const log = (level, color, args) => {
84
85
  };
85
86
  const logger = {
86
87
  error: (...args) => {
87
- log('error', red, args);
88
+ log('error', colors.red, args);
88
89
  },
89
90
  warn: (...args) => {
90
- log('warn', yellow, args);
91
+ log('warn', colors.yellow, args);
91
92
  },
92
93
  info: (...args) => {
93
- log('info', green, args);
94
+ log('info', colors.green, args);
94
95
  },
95
96
  debug: (...args) => {
96
- log('debug', blue, args);
97
+ log('debug', colors.blue, args);
97
98
  },
98
99
  };
99
100
 
@@ -230,7 +231,14 @@ class SubscriptionManager {
230
231
  }
231
232
  const { url } = this.clients.get(id);
232
233
  logger.info(`[Subscription] Trigger ${id}`);
233
- yield axios.post(url, result);
234
+ const response = yield fetch(url, {
235
+ method: 'POST',
236
+ body: JSON.stringify(result),
237
+ headers: {
238
+ 'Content-Type': 'application/json',
239
+ }
240
+ });
241
+ yield response.text();
234
242
  });
235
243
  }
236
244
  buildOperations() {
@@ -264,7 +272,9 @@ class SubscriptionManager {
264
272
 
265
273
  function createRouter(sofa) {
266
274
  logger.debug('[Sofa] Creating router');
267
- const router = new Trouter();
275
+ const router = Router({
276
+ base: sofa.basePath,
277
+ });
268
278
  const queryType = sofa.schema.getQueryType();
269
279
  const mutationType = sofa.schema.getMutationType();
270
280
  const subscriptionManager = new SubscriptionManager(sofa);
@@ -284,92 +294,75 @@ function createRouter(sofa) {
284
294
  }
285
295
  });
286
296
  }
287
- router.post('/webhook', ({ body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
288
- const { subscription, variables, url } = body;
297
+ router.post('/webhook', (request, contextValue) => __awaiter(this, void 0, void 0, function* () {
298
+ const { subscription, variables, url } = yield request.json();
289
299
  try {
290
300
  const result = yield subscriptionManager.start({
291
301
  subscription,
292
302
  variables,
293
303
  url,
294
304
  }, contextValue);
295
- return {
296
- type: 'result',
305
+ return new Response(JSON.stringify(result), {
297
306
  status: 200,
298
- statusMessage: 'OK',
299
- body: result,
300
- };
307
+ statusText: 'OK',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ },
311
+ });
301
312
  }
302
313
  catch (error) {
303
- return {
304
- type: 'error',
314
+ return new Response(JSON.stringify(error), {
305
315
  status: 500,
306
- statusMessage: 'Subscription failed',
307
- error,
308
- };
316
+ statusText: 'Subscription failed',
317
+ });
309
318
  }
310
319
  }));
311
- router.post('/webhook/:id', ({ body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
312
- const id = params.id;
320
+ router.post('/webhook/:id', (request, contextValue) => __awaiter(this, void 0, void 0, function* () {
321
+ var _a;
322
+ const id = (_a = request.params) === null || _a === void 0 ? void 0 : _a.id;
323
+ const body = yield request.json();
313
324
  const variables = body.variables;
314
325
  try {
315
326
  const result = yield subscriptionManager.update({
316
327
  id,
317
328
  variables,
318
329
  }, contextValue);
319
- return {
320
- type: 'result',
330
+ return new Response(JSON.stringify(result), {
321
331
  status: 200,
322
- statusMessage: 'OK',
323
- body: result,
324
- };
332
+ statusText: 'OK',
333
+ headers: {
334
+ 'Content-Type': 'application/json',
335
+ },
336
+ });
325
337
  }
326
338
  catch (error) {
327
- return {
328
- type: 'error',
339
+ return new Response(JSON.stringify(error), {
329
340
  status: 500,
330
- statusMessage: 'Subscription failed to update',
331
- error,
332
- };
341
+ statusText: 'Subscription failed to update',
342
+ });
333
343
  }
334
344
  }));
335
- router.delete('/webhook/:id', ({ params }) => __awaiter(this, void 0, void 0, function* () {
336
- const id = params.id;
345
+ router.delete('/webhook/:id', (request) => __awaiter(this, void 0, void 0, function* () {
346
+ var _b;
347
+ const id = (_b = request.params) === null || _b === void 0 ? void 0 : _b.id;
337
348
  try {
338
349
  const result = yield subscriptionManager.stop(id);
339
- return {
340
- type: 'result',
350
+ return new Response(JSON.stringify(result), {
341
351
  status: 200,
342
- statusMessage: 'OK',
343
- body: result,
344
- };
352
+ statusText: 'OK',
353
+ headers: {
354
+ 'Content-Type': 'application/json',
355
+ },
356
+ });
345
357
  }
346
358
  catch (error) {
347
- return {
348
- type: 'error',
359
+ return new Response(JSON.stringify(error), {
349
360
  status: 500,
350
- statusMessage: 'Subscription failed to stop',
351
- error,
352
- };
353
- }
354
- }));
355
- return ({ method, url, body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
356
- if (!url.startsWith(sofa.basePath)) {
357
- return null;
358
- }
359
- // trim base path and search
360
- const [slicedUrl] = url.slice(sofa.basePath.length).split('?');
361
- const trouterMethod = method.toUpperCase();
362
- const obj = router.find(trouterMethod, slicedUrl);
363
- for (const handler of obj.handlers) {
364
- return yield handler({
365
- url,
366
- body,
367
- params: obj.params,
368
- contextValue,
361
+ statusText: 'Subscription failed to stop',
369
362
  });
370
363
  }
371
- return null;
372
- });
364
+ }));
365
+ return router;
373
366
  }
374
367
  function createQueryRoute({ sofa, router, fieldName, }) {
375
368
  var _a, _b, _c, _d;
@@ -400,7 +393,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {
400
393
  path: (_c = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.path) !== null && _c !== void 0 ? _c : getPath(fieldName, isSingle && hasIdArgument),
401
394
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
402
395
  };
403
- router[route.method.toLocaleLowerCase()](route.path, useHandler({ info, route, fieldName, sofa, operation }));
396
+ router[route.method](route.path, useHandler({ info, route, fieldName, sofa, operation }));
404
397
  logger.debug(`[Router] ${fieldName} query available at ${route.method} ${route.path}`);
405
398
  return {
406
399
  document: operation,
@@ -433,7 +426,7 @@ function createMutationRoute({ sofa, router, fieldName, }) {
433
426
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
434
427
  };
435
428
  const { method, path } = route;
436
- router[method.toLowerCase()](path, useHandler({ info, route, fieldName, sofa, operation }));
429
+ router[method](path, useHandler({ info, route, fieldName, sofa, operation }));
437
430
  logger.debug(`[Router] ${fieldName} mutation available at ${method} ${path}`);
438
431
  return {
439
432
  document: operation,
@@ -444,11 +437,24 @@ function createMutationRoute({ sofa, router, fieldName, }) {
444
437
  function useHandler(config) {
445
438
  const { sofa, operation, fieldName } = config;
446
439
  const info = config.info;
447
- return ({ url, body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
440
+ return (request, contextValue) => __awaiter(this, void 0, void 0, function* () {
441
+ var _a;
442
+ let body = {};
443
+ if (request.body != null) {
444
+ const strBody = yield request.text();
445
+ if (strBody) {
446
+ body = JSON.parse(strBody);
447
+ }
448
+ }
448
449
  const variableValues = info.variables.reduce((variables, variable) => {
449
450
  const name = variable.variable.name.value;
450
451
  const value = parseVariable({
451
- value: pickParam({ url, body, params, name }),
452
+ value: pickParam({
453
+ url: request.url,
454
+ body,
455
+ params: request.params || {},
456
+ name,
457
+ }),
452
458
  variable,
453
459
  schema: sofa.schema,
454
460
  });
@@ -466,27 +472,26 @@ function useHandler(config) {
466
472
  });
467
473
  if (result.errors) {
468
474
  const defaultErrorHandler = (errors) => {
469
- return {
470
- type: 'error',
475
+ return new Response(errors[0], {
471
476
  status: 500,
472
- error: errors[0],
473
- };
477
+ });
474
478
  };
475
479
  const errorHandler = sofa.errorHandler || defaultErrorHandler;
476
480
  return errorHandler(result.errors);
477
481
  }
478
- return {
479
- type: 'result',
482
+ return new Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
480
483
  status: config.route.responseStatus,
481
- body: result.data && result.data[fieldName],
482
- };
484
+ headers: {
485
+ 'Content-Type': 'application/json',
486
+ },
487
+ });
483
488
  });
484
489
  }
485
490
  function getPath(fieldName, hasId = false) {
486
491
  return `/${convertName(fieldName)}${hasId ? '/:id' : ''}`;
487
492
  }
488
493
  function pickParam({ name, url, params, body, }) {
489
- if (params && params.hasOwnProperty(name)) {
494
+ if (name in params) {
490
495
  return params[name];
491
496
  }
492
497
  const searchParams = new URLSearchParams(url.split('?')[1]);
@@ -553,24 +558,8 @@ function extractsModels(schema) {
553
558
  }
554
559
  // it's dumb but let's leave it for now
555
560
  function isArrayOf(type, expected) {
556
- if (isOptionalList(type)) {
557
- return true;
558
- }
559
- if (isNonNullType(type) && isOptionalList(type.ofType)) {
560
- return true;
561
- }
562
- function isOptionalList(list) {
563
- if (isListType(list)) {
564
- if (list.ofType.name === expected.name) {
565
- return true;
566
- }
567
- if (isNonNullType(list.ofType) &&
568
- list.ofType.ofType.name === expected.name) {
569
- return true;
570
- }
571
- }
572
- }
573
- return false;
561
+ const typeNameInSdl = type.toString();
562
+ return (typeNameInSdl.includes('[') && typeNameInSdl.includes(expected.toString()));
574
563
  }
575
564
  function hasID(type) {
576
565
  return isObjectType(type) && !!type.getFields().id;
@@ -843,50 +832,10 @@ function isContextFn(context) {
843
832
  return typeof context === 'function';
844
833
  }
845
834
  function useSofa(_a) {
846
- var { context } = _a, config = __rest(_a, ["context"]);
847
- const invokeSofa = createSofaRouter(config);
848
- return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
849
- var _b;
850
- try {
851
- let contextValue = { req };
852
- if (context) {
853
- if (typeof context === 'function') {
854
- contextValue = yield context({ req, res });
855
- }
856
- else {
857
- contextValue = context;
858
- }
859
- }
860
- const response = yield invokeSofa({
861
- method: req.method,
862
- url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
863
- body: req.body,
864
- contextValue,
865
- });
866
- if (response == null) {
867
- next();
868
- }
869
- else {
870
- const headers = {
871
- 'Content-Type': 'application/json',
872
- };
873
- if (response.statusMessage) {
874
- res.writeHead(response.status, response.statusMessage, headers);
875
- }
876
- else {
877
- res.writeHead(response.status, headers);
878
- }
879
- if (response.type === 'result') {
880
- res.end(JSON.stringify(response.body));
881
- }
882
- if (response.type === 'error') {
883
- res.end(JSON.stringify(response.error));
884
- }
885
- }
886
- }
887
- catch (error) {
888
- next(error);
889
- }
835
+ var config = __rest(_a, ["context"]);
836
+ const sofaRouter = createSofaRouter(config);
837
+ return createServerAdapter({
838
+ handleRequest: sofaRouter.handle,
890
839
  });
891
840
  }
892
841
  function createSofaRouter(config) {
File without changes
@@ -9,8 +9,8 @@ export declare function OpenAPI({ schema, info, servers, components, security, t
9
9
  tags?: Record<string, any>[];
10
10
  }): {
11
11
  addRoute(info: RouteInfo, config?: {
12
- basePath?: string | undefined;
13
- } | undefined): void;
12
+ basePath?: string;
13
+ }): void;
14
14
  get(): any;
15
15
  save(filepath: string): void;
16
16
  };
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ import { GraphQLObjectType, GraphQLInputObjectType, GraphQLType } from 'graphql';
2
+ export declare function buildSchemaObjectFromType(type: GraphQLObjectType | GraphQLInputObjectType): any;
3
+ export declare function resolveFieldType(type: GraphQLType): any;
File without changes
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "sofa-api",
3
+ "version": "0.11.1",
4
+ "description": "Create REST APIs with GraphQL",
5
+ "sideEffects": false,
6
+ "peerDependencies": {
7
+ "graphql": "^0.13.2 || ^14.0.0 || ^15.0.0 || ^16.0.0"
8
+ },
9
+ "dependencies": {
10
+ "@graphql-tools/utils": "8.8.0",
11
+ "@types/js-yaml": "4.0.5",
12
+ "@whatwg-node/fetch": "^0.0.2",
13
+ "@whatwg-node/server": "^0.0.2",
14
+ "ansi-colors": "4.1.3",
15
+ "itty-router": "^2.6.1",
16
+ "js-yaml": "4.1.0",
17
+ "param-case": "3.0.4",
18
+ "title-case": "3.0.3",
19
+ "trouter": "3.2.0",
20
+ "tslib": "2.4.0",
21
+ "uuid": "8.3.2"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "Urigo/sofa"
26
+ },
27
+ "keywords": [
28
+ "api",
29
+ "rest",
30
+ "graphql",
31
+ "sofa"
32
+ ],
33
+ "author": {
34
+ "name": "Uri Goldshtein",
35
+ "email": "uri.goldshtein@gmail.com",
36
+ "url": "https://github.com/Urigo"
37
+ },
38
+ "license": "MIT",
39
+ "main": "index.js",
40
+ "module": "index.mjs",
41
+ "typings": "index.d.ts",
42
+ "typescript": {
43
+ "definition": "index.d.ts"
44
+ },
45
+ "exports": {
46
+ ".": {
47
+ "require": "./index.js",
48
+ "import": "./index.mjs"
49
+ },
50
+ "./*": {
51
+ "require": "./*.js",
52
+ "import": "./*.mjs"
53
+ },
54
+ "./package.json": "./package.json"
55
+ }
56
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ import { Request as IttyRequest, Router } from 'itty-router';
2
+ import type { Sofa } from './sofa';
3
+ export declare type ErrorHandler = (errors: ReadonlyArray<any>) => Response;
4
+ export declare function createRouter(sofa: Sofa): Router<IttyRequest, {}>;
@@ -1,6 +1,6 @@
1
1
  import { GraphQLSchema } from 'graphql';
2
2
  import { Ignore, ExecuteFn, OnRoute, Method } from './types';
3
- import { ErrorHandler } from './express';
3
+ import { ErrorHandler } from './router';
4
4
  interface RouteConfig {
5
5
  method?: Method;
6
6
  path?: string;
@@ -20,19 +20,11 @@ export declare class SubscriptionManager {
20
20
  private operations;
21
21
  private clients;
22
22
  constructor(sofa: Sofa);
23
- start(event: StartSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<{
24
- [key: string]: any;
25
- }, {
26
- [key: string]: any;
27
- }> | {
23
+ start(event: StartSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>> | {
28
24
  id: string;
29
25
  }>;
30
26
  stop(id: ID): Promise<StopSubscriptionResponse>;
31
- update(event: UpdateSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<{
32
- [key: string]: any;
33
- }, {
34
- [key: string]: any;
35
- }> | {
27
+ update(event: UpdateSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<import("graphql/jsutils/ObjMap").ObjMap<unknown>, import("graphql/jsutils/ObjMap").ObjMap<unknown>> | {
36
28
  id: string;
37
29
  }>;
38
30
  private execute;
File without changes
package/package.json CHANGED
@@ -1,53 +1,108 @@
1
1
  {
2
2
  "name": "sofa-api",
3
- "version": "0.11.0",
4
3
  "description": "Create REST APIs with GraphQL",
5
- "sideEffects": false,
6
- "peerDependencies": {
7
- "graphql": "^0.13.2 || ^14.0.0 || ^15.0.0"
4
+ "version": "0.11.2-alpha.0",
5
+ "buildOptions": {
6
+ "input": "./src/index.ts"
8
7
  },
9
- "dependencies": {
10
- "@graphql-tools/utils": "8.1.1",
11
- "@types/js-yaml": "4.0.2",
12
- "ansi-colors": "4.1.1",
13
- "axios": "0.21.1",
14
- "js-yaml": "4.1.0",
15
- "param-case": "3.0.4",
16
- "title-case": "3.0.3",
17
- "trouter": "3.2.0",
18
- "tslib": "2.3.1",
19
- "uuid": "8.3.2"
8
+ "sideEffects": false,
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.mjs",
11
+ "exports": {
12
+ ".": {
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.mjs"
15
+ },
16
+ "./*": {
17
+ "require": "./dist/*.js",
18
+ "import": "./dist/*.mjs"
19
+ }
20
20
  },
21
- "repository": {
22
- "type": "git",
23
- "url": "Urigo/sofa"
21
+ "typings": "dist/index.d.ts",
22
+ "typescript": {
23
+ "definition": "dist/index.d.ts"
24
24
  },
25
+ "license": "MIT",
25
26
  "keywords": [
26
27
  "api",
27
28
  "rest",
28
29
  "graphql",
29
30
  "sofa"
30
31
  ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "Urigo/sofa"
35
+ },
31
36
  "author": {
32
37
  "name": "Uri Goldshtein",
33
38
  "email": "uri.goldshtein@gmail.com",
34
39
  "url": "https://github.com/Urigo"
35
40
  },
36
- "license": "MIT",
37
- "main": "index.js",
38
- "module": "index.mjs",
39
- "typings": "index.d.ts",
40
- "typescript": {
41
- "definition": "index.d.ts"
41
+ "peerDependencies": {
42
+ "graphql": "^0.13.2 || ^14.0.0 || ^15.0.0 || ^16.0.0"
42
43
  },
43
- "exports": {
44
- ".": {
45
- "require": "./index.js",
46
- "import": "./index.mjs"
47
- },
48
- "./*": {
49
- "require": "./*.js",
50
- "import": "./*.mjs"
44
+ "dependencies": {
45
+ "@graphql-tools/utils": "8.12.0",
46
+ "ansi-colors": "4.1.3",
47
+ "@whatwg-node/fetch": "^0.4.3",
48
+ "@whatwg-node/server": "^0.4.1",
49
+ "itty-router": "^2.6.1",
50
+ "param-case": "3.0.4",
51
+ "title-case": "3.0.3",
52
+ "tslib": "2.4.0",
53
+ "uuid": "8.3.2"
54
+ },
55
+ "scripts": {
56
+ "start": "ts-node --project tsconfig.example.json example/index.ts",
57
+ "clean": "rm -rf dist",
58
+ "prebuild": "yarn clean",
59
+ "build": "bob build --single",
60
+ "test": "jest --no-watchman",
61
+ "prepare-release": "yarn build && yarn test",
62
+ "release": "yarn prepare-release && bob prepack && npm publish dist",
63
+ "ci:release:canary": "node bump.js && bob prepack && npm publish dist --tag alpha --access public",
64
+ "postinstall": "husky install"
65
+ },
66
+ "devDependencies": {
67
+ "@graphql-tools/schema": "9.0.4",
68
+ "@graphql-yoga/node": "2.13.13",
69
+ "@types/jest": "28.1.8",
70
+ "@types/node": "14.18.29",
71
+ "@types/swagger-ui-dist": "3.30.1",
72
+ "@types/uuid": "8.3.4",
73
+ "@types/yamljs": "0.2.31",
74
+ "bob-the-bundler": "1.7.3",
75
+ "bundlesize": "0.18.1",
76
+ "chalk": "^4",
77
+ "express": "4.18.1",
78
+ "express-graphql": "0.12.0",
79
+ "graphql": "16.6.0",
80
+ "graphql-subscriptions": "2.0.0",
81
+ "husky": "8.0.1",
82
+ "jest": "28.1.3",
83
+ "lint-staged": "13.0.3",
84
+ "prettier": "2.7.1",
85
+ "supertest": "6.2.4",
86
+ "swagger-ui-dist": "4.14.0",
87
+ "ts-jest": "28.0.8",
88
+ "ts-node": "10.9.1",
89
+ "typescript": "4.7.4"
90
+ },
91
+ "husky": {
92
+ "hooks": {
93
+ "pre-commit": "lint-staged"
94
+ }
95
+ },
96
+ "lint-staged": {
97
+ "*.{ts,js,md,json}": [
98
+ "prettier --write"
99
+ ]
100
+ },
101
+ "bundlesize": [
102
+ {
103
+ "path": "./dist/index.mjs",
104
+ "maxSize": "36 kB",
105
+ "compression": "none"
51
106
  }
52
- }
53
- }
107
+ ]
108
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "CommonJS"
5
+ },
6
+ "include": ["example"]
7
+ }
package/express.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { Sofa } from './sofa';
2
- import type { ContextValue } from './types';
3
- export declare type ErrorHandler = (errors: ReadonlyArray<any>) => RouterError;
4
- declare type RouterRequest = {
5
- method: string;
6
- url: string;
7
- body: any;
8
- contextValue: ContextValue;
9
- };
10
- declare type RouterResult = {
11
- type: 'result';
12
- status: number;
13
- statusMessage?: string;
14
- body: any;
15
- };
16
- declare type RouterError = {
17
- type: 'error';
18
- status: number;
19
- statusMessage?: string;
20
- error: any;
21
- };
22
- declare type RouterResponse = RouterResult | RouterError;
23
- declare type Router = (request: RouterRequest) => Promise<null | RouterResponse>;
24
- export declare function createRouter(sofa: Sofa): Router;
25
- export {};
package/index.d.ts DELETED
@@ -1,38 +0,0 @@
1
- /// <reference types="node" />
2
- import * as http from 'http';
3
- import type { ContextValue } from './types';
4
- import type { SofaConfig } from './sofa';
5
- export { OpenAPI } from './open-api';
6
- declare type Request = http.IncomingMessage & {
7
- method: string;
8
- url: string;
9
- originalUrl?: string;
10
- body?: any;
11
- };
12
- declare type NextFunction = (err?: any) => void;
13
- declare type Middleware = (req: Request, res: http.ServerResponse, next: NextFunction) => unknown;
14
- export declare type ContextFn = (init: {
15
- req: any;
16
- res: any;
17
- }) => ContextValue;
18
- export declare function isContextFn(context: any): context is ContextFn;
19
- interface SofaMiddlewareConfig extends SofaConfig {
20
- context?: ContextValue | ContextFn;
21
- }
22
- export declare function useSofa({ context, ...config }: SofaMiddlewareConfig): Middleware;
23
- export declare function createSofaRouter(config: SofaConfig): (request: {
24
- method: string;
25
- url: string;
26
- body: any;
27
- contextValue: ContextValue;
28
- }) => Promise<({
29
- type: "result";
30
- status: number;
31
- statusMessage?: string | undefined;
32
- body: any;
33
- } | {
34
- type: "error";
35
- status: number;
36
- statusMessage?: string | undefined;
37
- error: any;
38
- }) | null>;
@@ -1,3 +0,0 @@
1
- import { GraphQLObjectType, GraphQLInputObjectType, GraphQLOutputType, GraphQLNamedType } from 'graphql';
2
- export declare function buildSchemaObjectFromType(type: GraphQLObjectType | GraphQLInputObjectType): any;
3
- export declare function resolveFieldType(type: GraphQLOutputType | GraphQLNamedType): any;