sofa-api 0.11.2 → 0.12.0-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/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/index.d.ts CHANGED
@@ -1,38 +1,3 @@
1
- /// <reference types="node" />
2
- import * as http from 'http';
3
- import type { ContextValue } from './types';
4
1
  import type { SofaConfig } from './sofa';
5
2
  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>;
3
+ export declare function useSofa(config: SofaConfig): import("@whatwg-node/server").ServerAdapter<unknown, import("itty-router").Router<import("itty-router").Request & Request, {}>>;
package/index.js CHANGED
@@ -6,14 +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 crossUndiciFetch = require('cross-undici-fetch');
13
+ const fetch = require('@whatwg-node/fetch');
14
14
  const colors = _interopDefault(require('ansi-colors'));
15
- const jsYaml = require('js-yaml');
16
- const fs = require('fs');
15
+ const server = require('@whatwg-node/server');
17
16
  const titleCase = require('title-case');
18
17
 
19
18
  function getOperationInfo(doc) {
@@ -62,7 +61,7 @@ function resolveVariable({ value, type, schema, }) {
62
61
  return value;
63
62
  }
64
63
  if (type.kind === graphql.Kind.LIST_TYPE) {
65
- return (Array.isArray(value) ? value : [value]).map(val => resolveVariable({
64
+ return (Array.isArray(value) ? value : [value]).map((val) => resolveVariable({
66
65
  value: val,
67
66
  type: type.type,
68
67
  schema,
@@ -181,7 +180,7 @@ class SubscriptionManager {
181
180
  }
182
181
  return Object.assign(Object.assign({}, values), { [name]: value });
183
182
  }, {});
184
- const execution = yield graphql.subscribe({
183
+ const execution = yield this.sofa.subscribe({
185
184
  schema: this.sofa.schema,
186
185
  document,
187
186
  operationName,
@@ -236,12 +235,12 @@ class SubscriptionManager {
236
235
  }
237
236
  const { url } = this.clients.get(id);
238
237
  logger.info(`[Subscription] Trigger ${id}`);
239
- const response = yield crossUndiciFetch.fetch(url, {
238
+ const response = yield fetch.fetch(url, {
240
239
  method: 'POST',
241
240
  body: JSON.stringify(result),
242
241
  headers: {
243
242
  'Content-Type': 'application/json',
244
- }
243
+ },
245
244
  });
246
245
  yield response.text();
247
246
  });
@@ -277,7 +276,9 @@ class SubscriptionManager {
277
276
 
278
277
  function createRouter(sofa) {
279
278
  logger.debug('[Sofa] Creating router');
280
- const router = new Trouter();
279
+ const router = ittyRouter.Router({
280
+ base: sofa.basePath,
281
+ });
281
282
  const queryType = sofa.schema.getQueryType();
282
283
  const mutationType = sofa.schema.getMutationType();
283
284
  const subscriptionManager = new SubscriptionManager(sofa);
@@ -297,92 +298,77 @@ function createRouter(sofa) {
297
298
  }
298
299
  });
299
300
  }
300
- router.post('/webhook', ({ body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
301
- const { subscription, variables, url } = body;
301
+ router.post('/webhook', (request, serverContext) => tslib.__awaiter(this, void 0, void 0, function* () {
302
+ const { subscription, variables, url } = yield request.json();
302
303
  try {
304
+ const contextValue = yield sofa.contextFactory(serverContext);
303
305
  const result = yield subscriptionManager.start({
304
306
  subscription,
305
307
  variables,
306
308
  url,
307
309
  }, contextValue);
308
- return {
309
- type: 'result',
310
+ return new fetch.Response(JSON.stringify(result), {
310
311
  status: 200,
311
- statusMessage: 'OK',
312
- body: result,
313
- };
312
+ statusText: 'OK',
313
+ headers: {
314
+ 'Content-Type': 'application/json',
315
+ },
316
+ });
314
317
  }
315
318
  catch (error) {
316
- return {
317
- type: 'error',
319
+ return new fetch.Response(JSON.stringify(error), {
318
320
  status: 500,
319
- statusMessage: 'Subscription failed',
320
- error,
321
- };
321
+ statusText: 'Subscription failed',
322
+ });
322
323
  }
323
324
  }));
324
- router.post('/webhook/:id', ({ body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
325
- const id = params.id;
325
+ router.post('/webhook/:id', (request, serverContext) => tslib.__awaiter(this, void 0, void 0, function* () {
326
+ var _a;
327
+ const id = (_a = request.params) === null || _a === void 0 ? void 0 : _a.id;
328
+ const body = yield request.json();
326
329
  const variables = body.variables;
327
330
  try {
331
+ const contextValue = yield sofa.contextFactory(serverContext);
328
332
  const result = yield subscriptionManager.update({
329
333
  id,
330
334
  variables,
331
335
  }, contextValue);
332
- return {
333
- type: 'result',
336
+ return new fetch.Response(JSON.stringify(result), {
334
337
  status: 200,
335
- statusMessage: 'OK',
336
- body: result,
337
- };
338
+ statusText: 'OK',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ },
342
+ });
338
343
  }
339
344
  catch (error) {
340
- return {
341
- type: 'error',
345
+ return new fetch.Response(JSON.stringify(error), {
342
346
  status: 500,
343
- statusMessage: 'Subscription failed to update',
344
- error,
345
- };
347
+ statusText: 'Subscription failed to update',
348
+ });
346
349
  }
347
350
  }));
348
- router.delete('/webhook/:id', ({ params }) => tslib.__awaiter(this, void 0, void 0, function* () {
349
- 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;
350
354
  try {
351
355
  const result = yield subscriptionManager.stop(id);
352
- return {
353
- type: 'result',
356
+ return new fetch.Response(JSON.stringify(result), {
354
357
  status: 200,
355
- statusMessage: 'OK',
356
- body: result,
357
- };
358
+ statusText: 'OK',
359
+ headers: {
360
+ 'Content-Type': 'application/json',
361
+ },
362
+ });
358
363
  }
359
364
  catch (error) {
360
- return {
361
- type: 'error',
365
+ return new fetch.Response(JSON.stringify(error), {
362
366
  status: 500,
363
- statusMessage: 'Subscription failed to stop',
364
- error,
365
- };
366
- }
367
- }));
368
- return ({ method, url, body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
369
- if (!url.startsWith(sofa.basePath)) {
370
- return null;
371
- }
372
- // trim base path and search
373
- const [slicedUrl] = url.slice(sofa.basePath.length).split('?');
374
- const trouterMethod = method.toUpperCase();
375
- const obj = router.find(trouterMethod, slicedUrl);
376
- for (const handler of obj.handlers) {
377
- return yield handler({
378
- url,
379
- body,
380
- params: obj.params,
381
- contextValue,
367
+ statusText: 'Subscription failed to stop',
382
368
  });
383
369
  }
384
- return null;
385
- });
370
+ }));
371
+ return router;
386
372
  }
387
373
  function createQueryRoute({ sofa, router, fieldName, }) {
388
374
  var _a, _b, _c, _d;
@@ -413,7 +399,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {
413
399
  path: (_c = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.path) !== null && _c !== void 0 ? _c : getPath(fieldName, isSingle && hasIdArgument),
414
400
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
415
401
  };
416
- router[route.method.toLocaleLowerCase()](route.path, useHandler({ info, route, fieldName, sofa, operation }));
402
+ router[route.method](route.path, useHandler({ info, route, fieldName, sofa, operation }));
417
403
  logger.debug(`[Router] ${fieldName} query available at ${route.method} ${route.path}`);
418
404
  return {
419
405
  document: operation,
@@ -446,7 +432,7 @@ function createMutationRoute({ sofa, router, fieldName, }) {
446
432
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
447
433
  };
448
434
  const { method, path } = route;
449
- router[method.toLowerCase()](path, useHandler({ info, route, fieldName, sofa, operation }));
435
+ router[method](path, useHandler({ info, route, fieldName, sofa, operation }));
450
436
  logger.debug(`[Router] ${fieldName} mutation available at ${method} ${path}`);
451
437
  return {
452
438
  document: operation,
@@ -457,11 +443,24 @@ function createMutationRoute({ sofa, router, fieldName, }) {
457
443
  function useHandler(config) {
458
444
  const { sofa, operation, fieldName } = config;
459
445
  const info = config.info;
460
- return ({ url, body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
446
+ return (request, serverContext) => 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
+ }
461
455
  const variableValues = info.variables.reduce((variables, variable) => {
462
456
  const name = variable.variable.name.value;
463
457
  const value = parseVariable({
464
- value: pickParam({ url, body, params, name }),
458
+ value: pickParam({
459
+ url: request.url,
460
+ body,
461
+ params: request.params || {},
462
+ name,
463
+ }),
465
464
  variable,
466
465
  schema: sofa.schema,
467
466
  });
@@ -470,36 +469,36 @@ function useHandler(config) {
470
469
  }
471
470
  return Object.assign(Object.assign({}, variables), { [name]: value });
472
471
  }, {});
472
+ const contextValue = yield sofa.contextFactory(serverContext);
473
473
  const result = yield sofa.execute({
474
474
  schema: sofa.schema,
475
- source: graphql.print(operation),
475
+ document: operation,
476
476
  contextValue,
477
477
  variableValues,
478
478
  operationName: info.operation.name && info.operation.name.value,
479
479
  });
480
480
  if (result.errors) {
481
481
  const defaultErrorHandler = (errors) => {
482
- return {
483
- type: 'error',
482
+ return new fetch.Response(errors[0], {
484
483
  status: 500,
485
- error: errors[0],
486
- };
484
+ });
487
485
  };
488
486
  const errorHandler = sofa.errorHandler || defaultErrorHandler;
489
487
  return errorHandler(result.errors);
490
488
  }
491
- return {
492
- type: 'result',
489
+ return new fetch.Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
493
490
  status: config.route.responseStatus,
494
- body: result.data && result.data[fieldName],
495
- };
491
+ headers: {
492
+ 'Content-Type': 'application/json',
493
+ },
494
+ });
496
495
  });
497
496
  }
498
497
  function getPath(fieldName, hasId = false) {
499
498
  return `/${convertName(fieldName)}${hasId ? '/:id' : ''}`;
500
499
  }
501
500
  function pickParam({ name, url, params, body, }) {
502
- if (params && params.hasOwnProperty(name)) {
501
+ if (name in params) {
503
502
  return params[name];
504
503
  }
505
504
  const searchParams = new URLSearchParams(url.split('?')[1]);
@@ -519,9 +518,25 @@ function createSofa(config) {
519
518
  const depthLimit = config.depthLimit || 1;
520
519
  logger.debug(`[Sofa] models: ${models.join(', ')}`);
521
520
  logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
522
- return Object.assign({ execute: graphql.graphql, models,
521
+ return Object.assign({ execute: graphql.execute,
522
+ subscribe: graphql.subscribe,
523
+ models,
523
524
  ignore,
524
- depthLimit }, config);
525
+ depthLimit,
526
+ contextFactory(serverContext) {
527
+ if (config.context != null) {
528
+ if (isContextFn(config.context)) {
529
+ return config.context(serverContext);
530
+ }
531
+ else {
532
+ return config.context;
533
+ }
534
+ }
535
+ return serverContext;
536
+ } }, config);
537
+ }
538
+ function isContextFn(context) {
539
+ return typeof context === 'function';
525
540
  }
526
541
  // Objects and Unions are the only things that are used to define return types
527
542
  // and both might contain an ID
@@ -706,7 +721,7 @@ function resolveRequestBody(variables) {
706
721
  }
707
722
  const properties = {};
708
723
  const required = [];
709
- variables.forEach(variable => {
724
+ variables.forEach((variable) => {
710
725
  if (variable.type.kind === graphql.Kind.NON_NULL_TYPE) {
711
726
  required.push(variable.variable.name.value);
712
727
  }
@@ -762,7 +777,7 @@ function resolveDescription(schema, operation) {
762
777
  if (!isObjectTypeDefinitionNode(definitionNode)) {
763
778
  return '';
764
779
  }
765
- const fieldNode = definitionNode.fields.find(field => field.name.value === fieldName);
780
+ const fieldNode = definitionNode.fields.find((field) => field.name.value === fieldName);
766
781
  const descriptionDefinition = fieldNode && fieldNode.description;
767
782
  return descriptionDefinition && descriptionDefinition.value
768
783
  ? descriptionDefinition.value
@@ -815,84 +830,12 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
815
830
  get() {
816
831
  return swagger;
817
832
  },
818
- save(filepath) {
819
- const isJSON = /\.json$/i;
820
- const isYAML = /.ya?ml$/i;
821
- if (isJSON.test(filepath)) {
822
- writeOutput(filepath, JSON.stringify(swagger, null, 2));
823
- }
824
- else if (isYAML.test(filepath)) {
825
- writeOutput(filepath, jsYaml.dump(swagger));
826
- }
827
- else {
828
- throw new Error('We only support JSON and YAML files');
829
- }
830
- },
831
833
  };
832
- }
833
- function writeOutput(filepath, contents) {
834
- fs.writeFileSync(filepath, contents, {
835
- encoding: 'utf-8',
836
- });
837
834
  }
838
835
 
839
- function isContextFn(context) {
840
- return typeof context === 'function';
841
- }
842
- function useSofa(_a) {
843
- var { context } = _a, config = tslib.__rest(_a, ["context"]);
844
- const invokeSofa = createSofaRouter(config);
845
- return (req, res, next) => tslib.__awaiter(this, void 0, void 0, function* () {
846
- var _b;
847
- try {
848
- let contextValue = { req };
849
- if (context) {
850
- if (typeof context === 'function') {
851
- contextValue = yield context({ req, res });
852
- }
853
- else {
854
- contextValue = context;
855
- }
856
- }
857
- const response = yield invokeSofa({
858
- method: req.method,
859
- url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
860
- body: req.body,
861
- contextValue,
862
- });
863
- if (response == null) {
864
- next();
865
- }
866
- else {
867
- const headers = {
868
- 'Content-Type': 'application/json',
869
- };
870
- if (response.statusMessage) {
871
- res.writeHead(response.status, response.statusMessage, headers);
872
- }
873
- else {
874
- res.writeHead(response.status, headers);
875
- }
876
- if (response.type === 'result') {
877
- res.end(JSON.stringify(response.body));
878
- }
879
- if (response.type === 'error') {
880
- res.end(JSON.stringify(response.error));
881
- }
882
- }
883
- }
884
- catch (error) {
885
- next(error);
886
- }
887
- });
888
- }
889
- function createSofaRouter(config) {
890
- const sofa = createSofa(config);
891
- const router = createRouter(sofa);
892
- return router;
836
+ function useSofa(config) {
837
+ return server.createServerAdapter(createRouter(createSofa(config)));
893
838
  }
894
839
 
895
840
  exports.OpenAPI = OpenAPI;
896
- exports.createSofaRouter = createSofaRouter;
897
- exports.isContextFn = isContextFn;
898
841
  exports.useSofa = useSofa;
package/index.mjs CHANGED
@@ -1,13 +1,12 @@
1
- import { __awaiter, __asyncValues, __rest } from 'tslib';
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';
1
+ import { __awaiter, __asyncValues } from 'tslib';
2
+ import { getOperationAST, Kind, isScalarType, isEqualType, GraphQLBoolean, isInputObjectType, isObjectType, isNonNullType, execute, subscribe, getNamedType, isListType, isEnumType, parse, printType, isIntrospectionType } from 'graphql';
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 { fetch } from 'cross-undici-fetch';
7
+ import { fetch, Response } from '@whatwg-node/fetch';
8
8
  import colors from 'ansi-colors';
9
- import { dump } from 'js-yaml';
10
- import { writeFileSync } from 'fs';
9
+ import { createServerAdapter } from '@whatwg-node/server';
11
10
  import { titleCase } from 'title-case';
12
11
 
13
12
  function getOperationInfo(doc) {
@@ -56,7 +55,7 @@ function resolveVariable({ value, type, schema, }) {
56
55
  return value;
57
56
  }
58
57
  if (type.kind === Kind.LIST_TYPE) {
59
- return (Array.isArray(value) ? value : [value]).map(val => resolveVariable({
58
+ return (Array.isArray(value) ? value : [value]).map((val) => resolveVariable({
60
59
  value: val,
61
60
  type: type.type,
62
61
  schema,
@@ -175,7 +174,7 @@ class SubscriptionManager {
175
174
  }
176
175
  return Object.assign(Object.assign({}, values), { [name]: value });
177
176
  }, {});
178
- const execution = yield subscribe({
177
+ const execution = yield this.sofa.subscribe({
179
178
  schema: this.sofa.schema,
180
179
  document,
181
180
  operationName,
@@ -235,7 +234,7 @@ class SubscriptionManager {
235
234
  body: JSON.stringify(result),
236
235
  headers: {
237
236
  'Content-Type': 'application/json',
238
- }
237
+ },
239
238
  });
240
239
  yield response.text();
241
240
  });
@@ -271,7 +270,9 @@ class SubscriptionManager {
271
270
 
272
271
  function createRouter(sofa) {
273
272
  logger.debug('[Sofa] Creating router');
274
- const router = new Trouter();
273
+ const router = Router({
274
+ base: sofa.basePath,
275
+ });
275
276
  const queryType = sofa.schema.getQueryType();
276
277
  const mutationType = sofa.schema.getMutationType();
277
278
  const subscriptionManager = new SubscriptionManager(sofa);
@@ -291,92 +292,77 @@ function createRouter(sofa) {
291
292
  }
292
293
  });
293
294
  }
294
- router.post('/webhook', ({ body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
295
- const { subscription, variables, url } = body;
295
+ router.post('/webhook', (request, serverContext) => __awaiter(this, void 0, void 0, function* () {
296
+ const { subscription, variables, url } = yield request.json();
296
297
  try {
298
+ const contextValue = yield sofa.contextFactory(serverContext);
297
299
  const result = yield subscriptionManager.start({
298
300
  subscription,
299
301
  variables,
300
302
  url,
301
303
  }, contextValue);
302
- return {
303
- type: 'result',
304
+ return new Response(JSON.stringify(result), {
304
305
  status: 200,
305
- statusMessage: 'OK',
306
- body: result,
307
- };
306
+ statusText: 'OK',
307
+ headers: {
308
+ 'Content-Type': 'application/json',
309
+ },
310
+ });
308
311
  }
309
312
  catch (error) {
310
- return {
311
- type: 'error',
313
+ return new Response(JSON.stringify(error), {
312
314
  status: 500,
313
- statusMessage: 'Subscription failed',
314
- error,
315
- };
315
+ statusText: 'Subscription failed',
316
+ });
316
317
  }
317
318
  }));
318
- router.post('/webhook/:id', ({ body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
319
- const id = params.id;
319
+ router.post('/webhook/:id', (request, serverContext) => __awaiter(this, void 0, void 0, function* () {
320
+ var _a;
321
+ const id = (_a = request.params) === null || _a === void 0 ? void 0 : _a.id;
322
+ const body = yield request.json();
320
323
  const variables = body.variables;
321
324
  try {
325
+ const contextValue = yield sofa.contextFactory(serverContext);
322
326
  const result = yield subscriptionManager.update({
323
327
  id,
324
328
  variables,
325
329
  }, contextValue);
326
- return {
327
- type: 'result',
330
+ return new Response(JSON.stringify(result), {
328
331
  status: 200,
329
- statusMessage: 'OK',
330
- body: result,
331
- };
332
+ statusText: 'OK',
333
+ headers: {
334
+ 'Content-Type': 'application/json',
335
+ },
336
+ });
332
337
  }
333
338
  catch (error) {
334
- return {
335
- type: 'error',
339
+ return new Response(JSON.stringify(error), {
336
340
  status: 500,
337
- statusMessage: 'Subscription failed to update',
338
- error,
339
- };
341
+ statusText: 'Subscription failed to update',
342
+ });
340
343
  }
341
344
  }));
342
- router.delete('/webhook/:id', ({ params }) => __awaiter(this, void 0, void 0, function* () {
343
- 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;
344
348
  try {
345
349
  const result = yield subscriptionManager.stop(id);
346
- return {
347
- type: 'result',
350
+ return new Response(JSON.stringify(result), {
348
351
  status: 200,
349
- statusMessage: 'OK',
350
- body: result,
351
- };
352
+ statusText: 'OK',
353
+ headers: {
354
+ 'Content-Type': 'application/json',
355
+ },
356
+ });
352
357
  }
353
358
  catch (error) {
354
- return {
355
- type: 'error',
359
+ return new Response(JSON.stringify(error), {
356
360
  status: 500,
357
- statusMessage: 'Subscription failed to stop',
358
- error,
359
- };
360
- }
361
- }));
362
- return ({ method, url, body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
363
- if (!url.startsWith(sofa.basePath)) {
364
- return null;
365
- }
366
- // trim base path and search
367
- const [slicedUrl] = url.slice(sofa.basePath.length).split('?');
368
- const trouterMethod = method.toUpperCase();
369
- const obj = router.find(trouterMethod, slicedUrl);
370
- for (const handler of obj.handlers) {
371
- return yield handler({
372
- url,
373
- body,
374
- params: obj.params,
375
- contextValue,
361
+ statusText: 'Subscription failed to stop',
376
362
  });
377
363
  }
378
- return null;
379
- });
364
+ }));
365
+ return router;
380
366
  }
381
367
  function createQueryRoute({ sofa, router, fieldName, }) {
382
368
  var _a, _b, _c, _d;
@@ -407,7 +393,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {
407
393
  path: (_c = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.path) !== null && _c !== void 0 ? _c : getPath(fieldName, isSingle && hasIdArgument),
408
394
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
409
395
  };
410
- router[route.method.toLocaleLowerCase()](route.path, useHandler({ info, route, fieldName, sofa, operation }));
396
+ router[route.method](route.path, useHandler({ info, route, fieldName, sofa, operation }));
411
397
  logger.debug(`[Router] ${fieldName} query available at ${route.method} ${route.path}`);
412
398
  return {
413
399
  document: operation,
@@ -440,7 +426,7 @@ function createMutationRoute({ sofa, router, fieldName, }) {
440
426
  responseStatus: (_d = routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.responseStatus) !== null && _d !== void 0 ? _d : 200,
441
427
  };
442
428
  const { method, path } = route;
443
- router[method.toLowerCase()](path, useHandler({ info, route, fieldName, sofa, operation }));
429
+ router[method](path, useHandler({ info, route, fieldName, sofa, operation }));
444
430
  logger.debug(`[Router] ${fieldName} mutation available at ${method} ${path}`);
445
431
  return {
446
432
  document: operation,
@@ -451,11 +437,24 @@ function createMutationRoute({ sofa, router, fieldName, }) {
451
437
  function useHandler(config) {
452
438
  const { sofa, operation, fieldName } = config;
453
439
  const info = config.info;
454
- return ({ url, body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
440
+ return (request, serverContext) => __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
+ }
455
449
  const variableValues = info.variables.reduce((variables, variable) => {
456
450
  const name = variable.variable.name.value;
457
451
  const value = parseVariable({
458
- value: pickParam({ url, body, params, name }),
452
+ value: pickParam({
453
+ url: request.url,
454
+ body,
455
+ params: request.params || {},
456
+ name,
457
+ }),
459
458
  variable,
460
459
  schema: sofa.schema,
461
460
  });
@@ -464,36 +463,36 @@ function useHandler(config) {
464
463
  }
465
464
  return Object.assign(Object.assign({}, variables), { [name]: value });
466
465
  }, {});
466
+ const contextValue = yield sofa.contextFactory(serverContext);
467
467
  const result = yield sofa.execute({
468
468
  schema: sofa.schema,
469
- source: print(operation),
469
+ document: operation,
470
470
  contextValue,
471
471
  variableValues,
472
472
  operationName: info.operation.name && info.operation.name.value,
473
473
  });
474
474
  if (result.errors) {
475
475
  const defaultErrorHandler = (errors) => {
476
- return {
477
- type: 'error',
476
+ return new Response(errors[0], {
478
477
  status: 500,
479
- error: errors[0],
480
- };
478
+ });
481
479
  };
482
480
  const errorHandler = sofa.errorHandler || defaultErrorHandler;
483
481
  return errorHandler(result.errors);
484
482
  }
485
- return {
486
- type: 'result',
483
+ return new Response(JSON.stringify((_a = result.data) === null || _a === void 0 ? void 0 : _a[fieldName]), {
487
484
  status: config.route.responseStatus,
488
- body: result.data && result.data[fieldName],
489
- };
485
+ headers: {
486
+ 'Content-Type': 'application/json',
487
+ },
488
+ });
490
489
  });
491
490
  }
492
491
  function getPath(fieldName, hasId = false) {
493
492
  return `/${convertName(fieldName)}${hasId ? '/:id' : ''}`;
494
493
  }
495
494
  function pickParam({ name, url, params, body, }) {
496
- if (params && params.hasOwnProperty(name)) {
495
+ if (name in params) {
497
496
  return params[name];
498
497
  }
499
498
  const searchParams = new URLSearchParams(url.split('?')[1]);
@@ -513,9 +512,25 @@ function createSofa(config) {
513
512
  const depthLimit = config.depthLimit || 1;
514
513
  logger.debug(`[Sofa] models: ${models.join(', ')}`);
515
514
  logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
516
- return Object.assign({ execute: graphql, models,
515
+ return Object.assign({ execute,
516
+ subscribe,
517
+ models,
517
518
  ignore,
518
- depthLimit }, config);
519
+ depthLimit,
520
+ contextFactory(serverContext) {
521
+ if (config.context != null) {
522
+ if (isContextFn(config.context)) {
523
+ return config.context(serverContext);
524
+ }
525
+ else {
526
+ return config.context;
527
+ }
528
+ }
529
+ return serverContext;
530
+ } }, config);
531
+ }
532
+ function isContextFn(context) {
533
+ return typeof context === 'function';
519
534
  }
520
535
  // Objects and Unions are the only things that are used to define return types
521
536
  // and both might contain an ID
@@ -700,7 +715,7 @@ function resolveRequestBody(variables) {
700
715
  }
701
716
  const properties = {};
702
717
  const required = [];
703
- variables.forEach(variable => {
718
+ variables.forEach((variable) => {
704
719
  if (variable.type.kind === Kind.NON_NULL_TYPE) {
705
720
  required.push(variable.variable.name.value);
706
721
  }
@@ -756,7 +771,7 @@ function resolveDescription(schema, operation) {
756
771
  if (!isObjectTypeDefinitionNode(definitionNode)) {
757
772
  return '';
758
773
  }
759
- const fieldNode = definitionNode.fields.find(field => field.name.value === fieldName);
774
+ const fieldNode = definitionNode.fields.find((field) => field.name.value === fieldName);
760
775
  const descriptionDefinition = fieldNode && fieldNode.description;
761
776
  return descriptionDefinition && descriptionDefinition.value
762
777
  ? descriptionDefinition.value
@@ -809,81 +824,11 @@ function OpenAPI({ schema, info, servers, components, security, tags, }) {
809
824
  get() {
810
825
  return swagger;
811
826
  },
812
- save(filepath) {
813
- const isJSON = /\.json$/i;
814
- const isYAML = /.ya?ml$/i;
815
- if (isJSON.test(filepath)) {
816
- writeOutput(filepath, JSON.stringify(swagger, null, 2));
817
- }
818
- else if (isYAML.test(filepath)) {
819
- writeOutput(filepath, dump(swagger));
820
- }
821
- else {
822
- throw new Error('We only support JSON and YAML files');
823
- }
824
- },
825
827
  };
826
- }
827
- function writeOutput(filepath, contents) {
828
- writeFileSync(filepath, contents, {
829
- encoding: 'utf-8',
830
- });
831
828
  }
832
829
 
833
- function isContextFn(context) {
834
- return typeof context === 'function';
835
- }
836
- function useSofa(_a) {
837
- var { context } = _a, config = __rest(_a, ["context"]);
838
- const invokeSofa = createSofaRouter(config);
839
- return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
840
- var _b;
841
- try {
842
- let contextValue = { req };
843
- if (context) {
844
- if (typeof context === 'function') {
845
- contextValue = yield context({ req, res });
846
- }
847
- else {
848
- contextValue = context;
849
- }
850
- }
851
- const response = yield invokeSofa({
852
- method: req.method,
853
- url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
854
- body: req.body,
855
- contextValue,
856
- });
857
- if (response == null) {
858
- next();
859
- }
860
- else {
861
- const headers = {
862
- 'Content-Type': 'application/json',
863
- };
864
- if (response.statusMessage) {
865
- res.writeHead(response.status, response.statusMessage, headers);
866
- }
867
- else {
868
- res.writeHead(response.status, headers);
869
- }
870
- if (response.type === 'result') {
871
- res.end(JSON.stringify(response.body));
872
- }
873
- if (response.type === 'error') {
874
- res.end(JSON.stringify(response.error));
875
- }
876
- }
877
- }
878
- catch (error) {
879
- next(error);
880
- }
881
- });
882
- }
883
- function createSofaRouter(config) {
884
- const sofa = createSofa(config);
885
- const router = createRouter(sofa);
886
- return router;
830
+ function useSofa(config) {
831
+ return createServerAdapter(createRouter(createSofa(config)));
887
832
  }
888
833
 
889
- export { OpenAPI, createSofaRouter, isContextFn, useSofa };
834
+ export { OpenAPI, useSofa };
@@ -9,8 +9,7 @@ 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
- save(filepath: string): void;
16
15
  };
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "sofa-api",
3
- "version": "0.11.2",
3
+ "version": "0.12.0-alpha.0",
4
4
  "description": "Create REST APIs with GraphQL",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
7
  "graphql": "^0.13.2 || ^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
10
- "@graphql-tools/utils": "8.6.5",
11
- "@types/js-yaml": "4.0.5",
12
- "ansi-colors": "4.1.1",
13
- "cross-undici-fetch": "0.1.28",
14
- "js-yaml": "4.1.0",
10
+ "@graphql-tools/utils": "8.12.0",
11
+ "@whatwg-node/fetch": "^0.4.3",
12
+ "@whatwg-node/server": "^0.4.1",
13
+ "ansi-colors": "4.1.3",
14
+ "itty-router": "^2.6.1",
15
15
  "param-case": "3.0.4",
16
16
  "title-case": "3.0.3",
17
- "trouter": "3.2.0",
18
- "tslib": "2.3.1",
17
+ "tslib": "2.4.0",
19
18
  "uuid": "8.3.2"
20
19
  },
21
20
  "repository": {
@@ -51,4 +50,4 @@
51
50
  },
52
51
  "./package.json": "./package.json"
53
52
  }
54
- }
53
+ }
package/router.d.ts ADDED
@@ -0,0 +1,6 @@
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
+ declare type SofaRequest = IttyRequest & Request;
5
+ export declare function createRouter(sofa: Sofa): Router<SofaRequest, {}>;
6
+ export {};
package/sofa.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { GraphQLSchema } from 'graphql';
2
- import { Ignore, ExecuteFn, OnRoute, Method } from './types';
3
- import { ErrorHandler } from './express';
1
+ import { GraphQLSchema, subscribe, execute } from 'graphql';
2
+ import { Ignore, OnRoute, Method, ContextFn, ContextValue } from './types';
3
+ import { ErrorHandler } from './router';
4
4
  interface RouteConfig {
5
5
  method?: Method;
6
6
  path?: string;
@@ -14,7 +14,8 @@ export interface Route {
14
14
  export interface SofaConfig {
15
15
  basePath: string;
16
16
  schema: GraphQLSchema;
17
- execute?: ExecuteFn;
17
+ execute?: typeof execute;
18
+ subscribe?: typeof subscribe;
18
19
  /**
19
20
  * Treats an Object with an ID as not a model.
20
21
  * @example ["User", "Message.author"]
@@ -27,6 +28,7 @@ export interface SofaConfig {
27
28
  * Overwrites the default HTTP route.
28
29
  */
29
30
  routes?: Record<string, RouteConfig>;
31
+ context?: ContextFn | ContextValue;
30
32
  }
31
33
  export interface Sofa {
32
34
  basePath: string;
@@ -35,9 +37,12 @@ export interface Sofa {
35
37
  ignore: Ignore;
36
38
  depthLimit: number;
37
39
  routes?: Record<string, RouteConfig>;
38
- execute: ExecuteFn;
40
+ execute: typeof execute;
41
+ subscribe: typeof subscribe;
39
42
  onRoute?: OnRoute;
40
43
  errorHandler?: ErrorHandler;
44
+ contextFactory: ContextFn;
41
45
  }
42
46
  export declare function createSofa(config: SofaConfig): Sofa;
47
+ export declare function isContextFn(context: any): context is ContextFn;
43
48
  export {};
package/types.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { GraphQLArgs, ExecutionResult, DocumentNode } from 'graphql';
1
+ import { DocumentNode } from 'graphql';
2
2
  export declare type ContextValue = Record<string, any>;
3
3
  export declare type Ignore = string[];
4
- export declare type ExecuteFn = (args: GraphQLArgs) => Promise<ExecutionResult<any>>;
5
4
  export declare type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
6
5
  export interface RouteInfo {
7
6
  document: DocumentNode;
@@ -9,3 +8,7 @@ export interface RouteInfo {
9
8
  method: Method;
10
9
  }
11
10
  export declare type OnRoute = (info: RouteInfo) => void;
11
+ export declare type ContextFn = (init: {
12
+ req: any;
13
+ res: any;
14
+ }) => Promise<ContextValue> | ContextValue;
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 {};