New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

sofa-api

Package Overview
Dependencies
Maintainers
4
Versions
120
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sofa-api - npm Package Compare versions

Comparing version 0.9.1 to 1.0.0

28

express.d.ts

@@ -1,7 +0,25 @@

/// <reference types="node" />
import * as http from 'http';
import { Router } from 'express';
import { Sofa } from './sofa';
export declare type ErrorHandler = (res: http.ServerResponse, errors: ReadonlyArray<any>) => void;
export declare type ExpressMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
import { ContextValue } from './types';
export declare type ErrorHandler = (errors: ReadonlyArray<any>) => RouterError;
declare type RouterRequest = {
method: string;
url: string;
body: any;
contextValue: ContextValue;
};
declare type RouterResult = {
type: 'result';
status: number;
statusMessage?: string;
body: any;
};
declare type RouterError = {
type: 'error';
status: number;
statusMessage?: string;
error: any;
};
declare type RouterResponse = RouterResult | RouterError;
declare type Router = (request: RouterRequest) => Promise<null | RouterResponse>;
export declare function createRouter(sofa: Sofa): Router;
export {};

494

index.cjs.js

@@ -8,10 +8,9 @@ 'use strict';

const tslib = require('tslib');
const express = require('express');
const graphql = require('graphql');
const Trouter = require('trouter');
const utils = require('@graphql-tools/utils');
const paramCase = require('param-case');
const colors = require('ansi-colors');
const uuid = require('uuid');
const axios = _interopDefault(require('axios'));
const iterall = require('iterall');
const colors = require('ansi-colors');
const jsYaml = require('js-yaml');

@@ -40,113 +39,2 @@ const fs = require('fs');

var _a;
const levels = ['error', 'warn', 'info', 'debug'];
const toLevel = (string) => levels.includes(string) ? string : null;
const currentLevel = process.env.SOFA_DEBUG
? 'debug'
: (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
const log = (level, color, args) => {
if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
console.log(`${color(level)}:`, ...args);
}
};
const logger = {
error: (...args) => {
log('error', colors.red, args);
},
warn: (...args) => {
log('warn', colors.yellow, args);
},
info: (...args) => {
log('info', colors.green, args);
},
debug: (...args) => {
log('debug', colors.blue, args);
},
};
function createSofa(config) {
logger.debug('[Sofa] Created');
const models = extractsModels(config.schema);
const ignore = config.ignore || [];
const depthLimit = config.depthLimit || 1;
logger.debug(`[Sofa] models: ${models.join(', ')}`);
logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
return Object.assign({ context({ req }) {
return { req };
}, execute: graphql.graphql, models,
ignore,
depthLimit }, config);
}
// Objects and Unions are the only things that are used to define return types
// and both might contain an ID
// We don't treat Unions as models because
// they might represent an Object that is not a model
// We check it later, when an operation is being built
function extractsModels(schema) {
const modelMap = {};
const query = schema.getQueryType();
const fields = query.getFields();
// if Query[type] (no args) and Query[type](just id as an argument)
// loop through every field
for (const fieldName in fields) {
const field = fields[fieldName];
const namedType = graphql.getNamedType(field.type);
if (hasID(namedType)) {
if (!modelMap[namedType.name]) {
modelMap[namedType.name] = {};
}
if (isArrayOf(field.type, namedType)) {
// check if type is a list
// check if name of a field matches a name of a named type (in plural)
// check if has no non-optional arguments
// add to registry with `list: true`
const sameName = isNameEqual(field.name, namedType.name + 's');
const allOptionalArguments = !field.args.some((arg) => graphql.isNonNullType(arg.type));
modelMap[namedType.name].list = sameName && allOptionalArguments;
}
else if (graphql.isObjectType(field.type) ||
(graphql.isNonNullType(field.type) && graphql.isObjectType(field.type.ofType))) {
// check if type is a graphql object type
// check if name of a field matches with name of an object type
// check if has only one argument named `id`
// add to registry with `single: true`
const sameName = isNameEqual(field.name, namedType.name);
const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
modelMap[namedType.name].single = sameName && hasIdArgument;
}
}
}
return Object.keys(modelMap).filter((name) => modelMap[name].list && modelMap[name].single);
}
// it's dumb but let's leave it for now
function isArrayOf(type, expected) {
if (isOptionalList(type)) {
return true;
}
if (graphql.isNonNullType(type) && isOptionalList(type.ofType)) {
return true;
}
function isOptionalList(list) {
if (graphql.isListType(list)) {
if (list.ofType.name === expected.name) {
return true;
}
if (graphql.isNonNullType(list.ofType) &&
list.ofType.ofType.name === expected.name) {
return true;
}
}
}
return false;
}
function hasID(type) {
return graphql.isObjectType(type) && !!type.getFields().id;
}
function isNameEqual(a, b) {
return convertName(a) === convertName(b);
}
function isContextFn(context) {
return typeof context === 'function';
}
function parseVariable({ value, variable, schema, }) {

@@ -194,2 +82,31 @@ if (isNil(value)) {

var _a;
const levels = ['error', 'warn', 'info', 'debug'];
const toLevel = (string) => levels.includes(string) ? string : null;
const currentLevel = process.env.SOFA_DEBUG
? 'debug'
: (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
const log = (level, color, args) => {
if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
console.log(`${color(level)}:`, ...args);
}
};
const logger = {
error: (...args) => {
log('error', colors.red, args);
},
warn: (...args) => {
log('warn', colors.yellow, args);
},
info: (...args) => {
log('info', colors.green, args);
},
debug: (...args) => {
log('debug', colors.blue, args);
},
};
function isAsyncIterable(obj) {
return typeof obj[Symbol.asyncIterator] === 'function';
}
class SubscriptionManager {

@@ -202,3 +119,3 @@ constructor(sofa) {

}
start(event, { req, res, }) {
start(event, contextValue) {
return tslib.__awaiter(this, void 0, void 0, function* () {

@@ -219,4 +136,3 @@ const id = uuid.v4();

variables,
req,
res,
contextValue,
});

@@ -243,3 +159,3 @@ if (typeof result !== 'undefined') {

}
update(event, { req, res, }) {
update(event, contextValue) {
return tslib.__awaiter(this, void 0, void 0, function* () {

@@ -257,9 +173,6 @@ const { variables, id } = event;

variables,
}, {
req,
res,
});
}, contextValue);
});
}
execute({ id, document, name, url, operationName, variables, req, res, }) {
execute({ id, document, name, url, operationName, variables, contextValue, }) {
return tslib.__awaiter(this, void 0, void 0, function* () {

@@ -278,5 +191,2 @@ const variableNodes = this.operations.get(name).variables;

}, {});
const C = isContextFn(this.sofa.context)
? yield this.sofa.context({ req, res })
: this.sofa.context;
const execution = yield graphql.subscribe({

@@ -287,5 +197,5 @@ schema: this.sofa.schema,

variableValues,
contextValue: C,
contextValue,
});
if (iterall.isAsyncIterable(execution)) {
if (isAsyncIterable(execution)) {
// successful

@@ -299,14 +209,27 @@ // add execution to clients

// success
iterall.forAwaitEach(execution, (result) => tslib.__awaiter(this, void 0, void 0, function* () {
yield this.sendData({
id,
result,
});
})).then(() => {
(() => tslib.__awaiter(this, void 0, void 0, function* () {
var e_1, _a;
try {
for (var execution_1 = tslib.__asyncValues(execution), execution_1_1; execution_1_1 = yield execution_1.next(), !execution_1_1.done;) {
const result = execution_1_1.value;
yield this.sendData({
id,
result,
});
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (execution_1_1 && !execution_1_1.done && (_a = execution_1.return)) yield _a.call(execution_1);
}
finally { if (e_1) throw e_1.error; }
}
}))().then(() => {
// completes
this.stop(id);
this.clients.delete(id);
}, (e) => {
logger.info(`Subscription #${id} closed`);
logger.error(e);
this.stop(id);
this.clients.delete(id);
});

@@ -360,3 +283,3 @@ }

logger.debug('[Sofa] Creating router');
const router = express.Router();
const router = new Trouter();
const queryType = sofa.schema.getQueryType();

@@ -381,4 +304,4 @@ const mutationType = sofa.schema.getMutationType();

}
router.post('/webhook', useAsync((req, res) => tslib.__awaiter(this, void 0, void 0, function* () {
const { subscription, variables, url } = req.body;
router.post('/webhook', ({ body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
const { subscription, variables, url } = body;
try {

@@ -389,18 +312,22 @@ const result = yield subscriptionManager.start({

url,
}, { req, res });
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
}, contextValue);
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(e));
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed',
error,
};
}
})));
router.post('/webhook/:id', useAsync((req, res) => tslib.__awaiter(this, void 0, void 0, function* () {
const id = req.params.id;
const variables = req.body.variables;
}));
router.post('/webhook/:id', ({ body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
const id = params.id;
const variables = body.variables;
try {

@@ -410,35 +337,56 @@ const result = yield subscriptionManager.update({

variables,
}, {
req,
res,
});
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
}, contextValue);
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed to update', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(e));
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed to update',
error,
};
}
})));
router.delete('/webhook/:id', useAsync((req, res) => tslib.__awaiter(this, void 0, void 0, function* () {
const id = req.params.id;
}));
router.delete('/webhook/:id', ({ params }) => tslib.__awaiter(this, void 0, void 0, function* () {
const id = params.id;
try {
const result = yield subscriptionManager.stop(id);
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed to stop', {
'Content-Type': 'application/json',
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed to stop',
error,
};
}
}));
return ({ method, url, body, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
if (!url.startsWith(sofa.basePath)) {
return null;
}
const slicedUrl = url.slice(sofa.basePath.length);
const trouterMethod = method.toUpperCase();
const obj = router.find(trouterMethod, slicedUrl);
for (const handler of obj.handlers) {
return yield handler({
url,
body,
params: obj.params,
contextValue,
});
res.end(JSON.stringify(e));
}
})));
return router;
return null;
});
}

@@ -515,7 +463,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {

const info = config.info;
return useAsync((req, res) => tslib.__awaiter(this, void 0, void 0, function* () {
return ({ url, body, params, contextValue }) => tslib.__awaiter(this, void 0, void 0, function* () {
const variableValues = info.variables.reduce((variables, variable) => {
const name = variable.variable.name.value;
const value = parseVariable({
value: pickParam(req, name),
value: pickParam({ url, body, params, name }),
variable,

@@ -529,5 +477,2 @@ schema: sofa.schema,

}, {});
const contextValue = isContextFn(sofa.context)
? yield sofa.context({ req, res })
: sofa.context;
const result = yield sofa.execute({

@@ -541,13 +486,18 @@ schema: sofa.schema,

if (result.errors) {
const defaultErrorHandler = (res, errors) => {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(errors[0]));
const defaultErrorHandler = (errors) => {
return {
type: 'error',
status: 500,
error: errors[0],
};
};
const errorHandler = sofa.errorHandler || defaultErrorHandler;
errorHandler(res, result.errors);
return;
return errorHandler(result.errors);
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result.data && result.data[fieldName]));
}));
return {
type: 'result',
status: 200,
body: result.data && result.data[fieldName],
};
});
}

@@ -557,18 +507,14 @@ function getPath(fieldName, hasId = false) {

}
function pickParam(req, name) {
if (req.params && req.params.hasOwnProperty(name)) {
return req.params[name];
function pickParam({ name, url, params, body, }) {
if (params && params.hasOwnProperty(name)) {
return params[name];
}
if (req.query && req.query.hasOwnProperty(name)) {
return req.query[name];
const searchParams = new URLSearchParams(url.split('?')[1]);
if (searchParams.has(name)) {
return searchParams.get(name);
}
if (req.body && req.body.hasOwnProperty(name)) {
return req.body[name];
if (body && body.hasOwnProperty(name)) {
return body[name];
}
}
function useAsync(handler) {
return (req, res, next) => {
Promise.resolve(handler(req, res)).catch((e) => next(e));
};
}
function produceMethod({ typeName, fieldName, methodMap, defaultValue, }) {

@@ -582,2 +528,82 @@ const path = `${typeName}.${fieldName}`;

function createSofa(config) {
logger.debug('[Sofa] Created');
const models = extractsModels(config.schema);
const ignore = config.ignore || [];
const depthLimit = config.depthLimit || 1;
logger.debug(`[Sofa] models: ${models.join(', ')}`);
logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
return Object.assign({ execute: graphql.graphql, models,
ignore,
depthLimit }, config);
}
// Objects and Unions are the only things that are used to define return types
// and both might contain an ID
// We don't treat Unions as models because
// they might represent an Object that is not a model
// We check it later, when an operation is being built
function extractsModels(schema) {
const modelMap = {};
const query = schema.getQueryType();
const fields = query.getFields();
// if Query[type] (no args) and Query[type](just id as an argument)
// loop through every field
for (const fieldName in fields) {
const field = fields[fieldName];
const namedType = graphql.getNamedType(field.type);
if (hasID(namedType)) {
if (!modelMap[namedType.name]) {
modelMap[namedType.name] = {};
}
if (isArrayOf(field.type, namedType)) {
// check if type is a list
// check if name of a field matches a name of a named type (in plural)
// check if has no non-optional arguments
// add to registry with `list: true`
const sameName = isNameEqual(field.name, namedType.name + 's');
const allOptionalArguments = !field.args.some((arg) => graphql.isNonNullType(arg.type));
modelMap[namedType.name].list = sameName && allOptionalArguments;
}
else if (graphql.isObjectType(field.type) ||
(graphql.isNonNullType(field.type) && graphql.isObjectType(field.type.ofType))) {
// check if type is a graphql object type
// check if name of a field matches with name of an object type
// check if has only one argument named `id`
// add to registry with `single: true`
const sameName = isNameEqual(field.name, namedType.name);
const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
modelMap[namedType.name].single = sameName && hasIdArgument;
}
}
}
return Object.keys(modelMap).filter((name) => modelMap[name].list && modelMap[name].single);
}
// it's dumb but let's leave it for now
function isArrayOf(type, expected) {
if (isOptionalList(type)) {
return true;
}
if (graphql.isNonNullType(type) && isOptionalList(type.ofType)) {
return true;
}
function isOptionalList(list) {
if (graphql.isListType(list)) {
if (list.ofType.name === expected.name) {
return true;
}
if (graphql.isNonNullType(list.ofType) &&
list.ofType.ofType.name === expected.name) {
return true;
}
}
}
return false;
}
function hasID(type) {
return graphql.isObjectType(type) && !!type.getFields().id;
}
function isNameEqual(a, b) {
return convertName(a) === convertName(b);
}
function mapToPrimitive(type) {

@@ -841,10 +867,62 @@ const formatMap = {

function useSofa(config) {
return createRouter(createSofa(config));
function isContextFn(context) {
return typeof context === 'function';
}
function useSofa(_a) {
var { context } = _a, config = tslib.__rest(_a, ["context"]);
const invokeSofa = createSofaRouter(config);
return (req, res, next) => tslib.__awaiter(this, void 0, void 0, function* () {
var _b;
try {
let contextValue = { req };
if (context) {
if (typeof context === 'function') {
contextValue = yield context({ req, res });
}
else {
contextValue = context;
}
}
const response = yield invokeSofa({
method: req.method,
url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
body: req.body,
contextValue,
});
if (response == null) {
next();
}
else {
const headers = {
'Content-Type': 'application/json',
};
if (response.statusMessage) {
res.writeHead(response.status, response.statusMessage, headers);
}
else {
res.writeHead(response.status, headers);
}
if (response.type === 'result') {
res.end(JSON.stringify(response.body));
}
if (response.type === 'error') {
res.end(JSON.stringify(response.error));
}
}
}
catch (error) {
next(error);
}
});
}
function createSofaRouter(config) {
const sofa = createSofa(config);
const router = createRouter(sofa);
return router;
}
exports.OpenAPI = OpenAPI;
exports.createSofa = createSofa;
exports.default = useSofa;
exports.createSofaRouter = createSofaRouter;
exports.isContextFn = isContextFn;
exports.useSofa = useSofa;
//# sourceMappingURL=index.cjs.js.map

@@ -1,6 +0,38 @@

import { Router } from 'express';
import { SofaConfig, createSofa } from './sofa';
/// <reference types="node" />
import * as http from 'http';
import type { ContextValue } from './types';
import type { SofaConfig } from './sofa';
export { OpenAPI } from './open-api';
declare function useSofa(config: SofaConfig): Router;
export { useSofa, createSofa };
export default useSofa;
declare type Request = http.IncomingMessage & {
method: string;
url: string;
originalUrl?: string;
body?: any;
};
declare type NextFunction = (err?: any) => void;
declare type Middleware = (req: Request, res: http.ServerResponse, next: NextFunction) => unknown;
export declare type ContextFn = (init: {
req: any;
res: any;
}) => ContextValue;
export declare function isContextFn(context: any): context is ContextFn;
interface SofaMiddlewareConfig extends SofaConfig {
context?: ContextValue | ContextFn;
}
export declare function useSofa({ context, ...config }: SofaMiddlewareConfig): Middleware;
export declare function createSofaRouter(config: SofaConfig): (request: {
method: string;
url: string;
body: any;
contextValue: Record<string, any>;
}) => Promise<{
type: "result";
status: number;
statusMessage?: string | undefined;
body: any;
} | {
type: "error";
status: number;
statusMessage?: string | undefined;
error: any;
} | null>;

@@ -1,10 +0,9 @@

import { __awaiter } from 'tslib';
import { Router } from 'express';
import { getOperationAST, graphql, getNamedType, isObjectType, isNonNullType, isListType, Kind, isScalarType, isEqualType, GraphQLBoolean, isInputObjectType, subscribe, print, isEnumType, parse, printType, isIntrospectionType } from 'graphql';
import { __awaiter, __asyncValues, __rest } from 'tslib';
import { getOperationAST, Kind, isScalarType, isEqualType, GraphQLBoolean, isInputObjectType, subscribe, isObjectType, isNonNullType, print, graphql, getNamedType, isListType, isEnumType, parse, printType, isIntrospectionType } from 'graphql';
import * as Trouter from 'trouter';
import { buildOperationNodeForField } from '@graphql-tools/utils';
import { paramCase } from 'param-case';
import { red, yellow, green, blue } from 'ansi-colors';
import { v4 } from 'uuid';
import axios from 'axios';
import { isAsyncIterable, forAwaitEach } from 'iterall';
import { red, yellow, green, blue } from 'ansi-colors';
import { dump } from 'js-yaml';

@@ -33,113 +32,2 @@ import { writeFileSync } from 'fs';

var _a;
const levels = ['error', 'warn', 'info', 'debug'];
const toLevel = (string) => levels.includes(string) ? string : null;
const currentLevel = process.env.SOFA_DEBUG
? 'debug'
: (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
const log = (level, color, args) => {
if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
console.log(`${color(level)}:`, ...args);
}
};
const logger = {
error: (...args) => {
log('error', red, args);
},
warn: (...args) => {
log('warn', yellow, args);
},
info: (...args) => {
log('info', green, args);
},
debug: (...args) => {
log('debug', blue, args);
},
};
function createSofa(config) {
logger.debug('[Sofa] Created');
const models = extractsModels(config.schema);
const ignore = config.ignore || [];
const depthLimit = config.depthLimit || 1;
logger.debug(`[Sofa] models: ${models.join(', ')}`);
logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
return Object.assign({ context({ req }) {
return { req };
}, execute: graphql, models,
ignore,
depthLimit }, config);
}
// Objects and Unions are the only things that are used to define return types
// and both might contain an ID
// We don't treat Unions as models because
// they might represent an Object that is not a model
// We check it later, when an operation is being built
function extractsModels(schema) {
const modelMap = {};
const query = schema.getQueryType();
const fields = query.getFields();
// if Query[type] (no args) and Query[type](just id as an argument)
// loop through every field
for (const fieldName in fields) {
const field = fields[fieldName];
const namedType = getNamedType(field.type);
if (hasID(namedType)) {
if (!modelMap[namedType.name]) {
modelMap[namedType.name] = {};
}
if (isArrayOf(field.type, namedType)) {
// check if type is a list
// check if name of a field matches a name of a named type (in plural)
// check if has no non-optional arguments
// add to registry with `list: true`
const sameName = isNameEqual(field.name, namedType.name + 's');
const allOptionalArguments = !field.args.some((arg) => isNonNullType(arg.type));
modelMap[namedType.name].list = sameName && allOptionalArguments;
}
else if (isObjectType(field.type) ||
(isNonNullType(field.type) && isObjectType(field.type.ofType))) {
// check if type is a graphql object type
// check if name of a field matches with name of an object type
// check if has only one argument named `id`
// add to registry with `single: true`
const sameName = isNameEqual(field.name, namedType.name);
const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
modelMap[namedType.name].single = sameName && hasIdArgument;
}
}
}
return Object.keys(modelMap).filter((name) => modelMap[name].list && modelMap[name].single);
}
// it's dumb but let's leave it for now
function isArrayOf(type, expected) {
if (isOptionalList(type)) {
return true;
}
if (isNonNullType(type) && isOptionalList(type.ofType)) {
return true;
}
function isOptionalList(list) {
if (isListType(list)) {
if (list.ofType.name === expected.name) {
return true;
}
if (isNonNullType(list.ofType) &&
list.ofType.ofType.name === expected.name) {
return true;
}
}
}
return false;
}
function hasID(type) {
return isObjectType(type) && !!type.getFields().id;
}
function isNameEqual(a, b) {
return convertName(a) === convertName(b);
}
function isContextFn(context) {
return typeof context === 'function';
}
function parseVariable({ value, variable, schema, }) {

@@ -187,2 +75,31 @@ if (isNil(value)) {

var _a;
const levels = ['error', 'warn', 'info', 'debug'];
const toLevel = (string) => levels.includes(string) ? string : null;
const currentLevel = process.env.SOFA_DEBUG
? 'debug'
: (_a = toLevel(process.env.SOFA_LOGGER_LEVEL)) !== null && _a !== void 0 ? _a : 'info';
const log = (level, color, args) => {
if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
console.log(`${color(level)}:`, ...args);
}
};
const logger = {
error: (...args) => {
log('error', red, args);
},
warn: (...args) => {
log('warn', yellow, args);
},
info: (...args) => {
log('info', green, args);
},
debug: (...args) => {
log('debug', blue, args);
},
};
function isAsyncIterable(obj) {
return typeof obj[Symbol.asyncIterator] === 'function';
}
class SubscriptionManager {

@@ -195,3 +112,3 @@ constructor(sofa) {

}
start(event, { req, res, }) {
start(event, contextValue) {
return __awaiter(this, void 0, void 0, function* () {

@@ -212,4 +129,3 @@ const id = v4();

variables,
req,
res,
contextValue,
});

@@ -236,3 +152,3 @@ if (typeof result !== 'undefined') {

}
update(event, { req, res, }) {
update(event, contextValue) {
return __awaiter(this, void 0, void 0, function* () {

@@ -250,9 +166,6 @@ const { variables, id } = event;

variables,
}, {
req,
res,
});
}, contextValue);
});
}
execute({ id, document, name, url, operationName, variables, req, res, }) {
execute({ id, document, name, url, operationName, variables, contextValue, }) {
return __awaiter(this, void 0, void 0, function* () {

@@ -271,5 +184,2 @@ const variableNodes = this.operations.get(name).variables;

}, {});
const C = isContextFn(this.sofa.context)
? yield this.sofa.context({ req, res })
: this.sofa.context;
const execution = yield subscribe({

@@ -280,3 +190,3 @@ schema: this.sofa.schema,

variableValues,
contextValue: C,
contextValue,
});

@@ -292,14 +202,27 @@ if (isAsyncIterable(execution)) {

// success
forAwaitEach(execution, (result) => __awaiter(this, void 0, void 0, function* () {
yield this.sendData({
id,
result,
});
})).then(() => {
(() => __awaiter(this, void 0, void 0, function* () {
var e_1, _a;
try {
for (var execution_1 = __asyncValues(execution), execution_1_1; execution_1_1 = yield execution_1.next(), !execution_1_1.done;) {
const result = execution_1_1.value;
yield this.sendData({
id,
result,
});
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (execution_1_1 && !execution_1_1.done && (_a = execution_1.return)) yield _a.call(execution_1);
}
finally { if (e_1) throw e_1.error; }
}
}))().then(() => {
// completes
this.stop(id);
this.clients.delete(id);
}, (e) => {
logger.info(`Subscription #${id} closed`);
logger.error(e);
this.stop(id);
this.clients.delete(id);
});

@@ -353,3 +276,3 @@ }

logger.debug('[Sofa] Creating router');
const router = Router();
const router = new Trouter();
const queryType = sofa.schema.getQueryType();

@@ -374,4 +297,4 @@ const mutationType = sofa.schema.getMutationType();

}
router.post('/webhook', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
const { subscription, variables, url } = req.body;
router.post('/webhook', ({ body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
const { subscription, variables, url } = body;
try {

@@ -382,18 +305,22 @@ const result = yield subscriptionManager.start({

url,
}, { req, res });
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
}, contextValue);
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(e));
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed',
error,
};
}
})));
router.post('/webhook/:id', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
const id = req.params.id;
const variables = req.body.variables;
}));
router.post('/webhook/:id', ({ body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
const id = params.id;
const variables = body.variables;
try {

@@ -403,35 +330,56 @@ const result = yield subscriptionManager.update({

variables,
}, {
req,
res,
});
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
}, contextValue);
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed to update', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(e));
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed to update',
error,
};
}
})));
router.delete('/webhook/:id', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
const id = req.params.id;
}));
router.delete('/webhook/:id', ({ params }) => __awaiter(this, void 0, void 0, function* () {
const id = params.id;
try {
const result = yield subscriptionManager.stop(id);
res.writeHead(200, 'OK', {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(result));
return {
type: 'result',
status: 200,
statusMessage: 'OK',
body: result,
};
}
catch (e) {
res.writeHead(500, 'Subscription failed to stop', {
'Content-Type': 'application/json',
catch (error) {
return {
type: 'error',
status: 500,
statusMessage: 'Subscription failed to stop',
error,
};
}
}));
return ({ method, url, body, contextValue }) => __awaiter(this, void 0, void 0, function* () {
if (!url.startsWith(sofa.basePath)) {
return null;
}
const slicedUrl = url.slice(sofa.basePath.length);
const trouterMethod = method.toUpperCase();
const obj = router.find(trouterMethod, slicedUrl);
for (const handler of obj.handlers) {
return yield handler({
url,
body,
params: obj.params,
contextValue,
});
res.end(JSON.stringify(e));
}
})));
return router;
return null;
});
}

@@ -508,7 +456,7 @@ function createQueryRoute({ sofa, router, fieldName, }) {

const info = config.info;
return useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
return ({ url, body, params, contextValue }) => __awaiter(this, void 0, void 0, function* () {
const variableValues = info.variables.reduce((variables, variable) => {
const name = variable.variable.name.value;
const value = parseVariable({
value: pickParam(req, name),
value: pickParam({ url, body, params, name }),
variable,

@@ -522,5 +470,2 @@ schema: sofa.schema,

}, {});
const contextValue = isContextFn(sofa.context)
? yield sofa.context({ req, res })
: sofa.context;
const result = yield sofa.execute({

@@ -534,13 +479,18 @@ schema: sofa.schema,

if (result.errors) {
const defaultErrorHandler = (res, errors) => {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(errors[0]));
const defaultErrorHandler = (errors) => {
return {
type: 'error',
status: 500,
error: errors[0],
};
};
const errorHandler = sofa.errorHandler || defaultErrorHandler;
errorHandler(res, result.errors);
return;
return errorHandler(result.errors);
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result.data && result.data[fieldName]));
}));
return {
type: 'result',
status: 200,
body: result.data && result.data[fieldName],
};
});
}

@@ -550,18 +500,14 @@ function getPath(fieldName, hasId = false) {

}
function pickParam(req, name) {
if (req.params && req.params.hasOwnProperty(name)) {
return req.params[name];
function pickParam({ name, url, params, body, }) {
if (params && params.hasOwnProperty(name)) {
return params[name];
}
if (req.query && req.query.hasOwnProperty(name)) {
return req.query[name];
const searchParams = new URLSearchParams(url.split('?')[1]);
if (searchParams.has(name)) {
return searchParams.get(name);
}
if (req.body && req.body.hasOwnProperty(name)) {
return req.body[name];
if (body && body.hasOwnProperty(name)) {
return body[name];
}
}
function useAsync(handler) {
return (req, res, next) => {
Promise.resolve(handler(req, res)).catch((e) => next(e));
};
}
function produceMethod({ typeName, fieldName, methodMap, defaultValue, }) {

@@ -575,2 +521,82 @@ const path = `${typeName}.${fieldName}`;

function createSofa(config) {
logger.debug('[Sofa] Created');
const models = extractsModels(config.schema);
const ignore = config.ignore || [];
const depthLimit = config.depthLimit || 1;
logger.debug(`[Sofa] models: ${models.join(', ')}`);
logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
return Object.assign({ execute: graphql, models,
ignore,
depthLimit }, config);
}
// Objects and Unions are the only things that are used to define return types
// and both might contain an ID
// We don't treat Unions as models because
// they might represent an Object that is not a model
// We check it later, when an operation is being built
function extractsModels(schema) {
const modelMap = {};
const query = schema.getQueryType();
const fields = query.getFields();
// if Query[type] (no args) and Query[type](just id as an argument)
// loop through every field
for (const fieldName in fields) {
const field = fields[fieldName];
const namedType = getNamedType(field.type);
if (hasID(namedType)) {
if (!modelMap[namedType.name]) {
modelMap[namedType.name] = {};
}
if (isArrayOf(field.type, namedType)) {
// check if type is a list
// check if name of a field matches a name of a named type (in plural)
// check if has no non-optional arguments
// add to registry with `list: true`
const sameName = isNameEqual(field.name, namedType.name + 's');
const allOptionalArguments = !field.args.some((arg) => isNonNullType(arg.type));
modelMap[namedType.name].list = sameName && allOptionalArguments;
}
else if (isObjectType(field.type) ||
(isNonNullType(field.type) && isObjectType(field.type.ofType))) {
// check if type is a graphql object type
// check if name of a field matches with name of an object type
// check if has only one argument named `id`
// add to registry with `single: true`
const sameName = isNameEqual(field.name, namedType.name);
const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
modelMap[namedType.name].single = sameName && hasIdArgument;
}
}
}
return Object.keys(modelMap).filter((name) => modelMap[name].list && modelMap[name].single);
}
// it's dumb but let's leave it for now
function isArrayOf(type, expected) {
if (isOptionalList(type)) {
return true;
}
if (isNonNullType(type) && isOptionalList(type.ofType)) {
return true;
}
function isOptionalList(list) {
if (isListType(list)) {
if (list.ofType.name === expected.name) {
return true;
}
if (isNonNullType(list.ofType) &&
list.ofType.ofType.name === expected.name) {
return true;
}
}
}
return false;
}
function hasID(type) {
return isObjectType(type) && !!type.getFields().id;
}
function isNameEqual(a, b) {
return convertName(a) === convertName(b);
}
function mapToPrimitive(type) {

@@ -834,8 +860,59 @@ const formatMap = {

function useSofa(config) {
return createRouter(createSofa(config));
function isContextFn(context) {
return typeof context === 'function';
}
function useSofa(_a) {
var { context } = _a, config = __rest(_a, ["context"]);
const invokeSofa = createSofaRouter(config);
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
var _b;
try {
let contextValue = { req };
if (context) {
if (typeof context === 'function') {
contextValue = yield context({ req, res });
}
else {
contextValue = context;
}
}
const response = yield invokeSofa({
method: req.method,
url: (_b = req.originalUrl) !== null && _b !== void 0 ? _b : req.url,
body: req.body,
contextValue,
});
if (response == null) {
next();
}
else {
const headers = {
'Content-Type': 'application/json',
};
if (response.statusMessage) {
res.writeHead(response.status, response.statusMessage, headers);
}
else {
res.writeHead(response.status, headers);
}
if (response.type === 'result') {
res.end(JSON.stringify(response.body));
}
if (response.type === 'error') {
res.end(JSON.stringify(response.error));
}
}
}
catch (error) {
next(error);
}
});
}
function createSofaRouter(config) {
const sofa = createSofa(config);
const router = createRouter(sofa);
return router;
}
export default useSofa;
export { OpenAPI, createSofa, useSofa };
export { OpenAPI, createSofaRouter, isContextFn, useSofa };
//# sourceMappingURL=index.esm.js.map
{
"name": "sofa-api",
"version": "0.9.1",
"version": "1.0.0",
"description": "Create REST APIs with GraphQL",
"sideEffects": false,
"peerDependencies": {
"express": "^4.0.0",
"graphql": "^0.13.2 || ^14.0.0 || ^15.0.0"
},
"dependencies": {
"@graphql-tools/utils": "7.2.4",
"@graphql-tools/utils": "7.2.5",
"@types/js-yaml": "4.0.0",
"ansi-colors": "4.1.1",
"axios": "0.21.1",
"iterall": "1.3.0",
"js-yaml": "4.0.0",
"param-case": "3.0.4",
"title-case": "3.0.3",
"trouter": "3.1.0",
"tslib": "2.1.0",

@@ -20,0 +19,0 @@ "uuid": "8.3.2"

@@ -18,5 +18,53 @@ [![sofa](https://user-images.githubusercontent.com/25294569/63839869-bfac8300-c988-11e9-978e-6b6c16c350de.gif)](https://sofa-api.com)

The most basic example possible:
Here's complete example with no dependency on frameworks, but also integratable with any of them:
```ts
```js
import http from 'http';
import getStream from 'get-stream';
import { createSofaRouter } from 'sofa-api';
const invokeSofa = createSofaRouter({
basePath: '/api',
schema,
});
const server = http.createServer(async (req, res) => {
try {
consr response = await invokeSofa({
method: req.method,
url: req.url,
body: JSON.parse(await getStream(req)),
contextValue: {
req
},
});
if (response) {
const headers = {
'Content-Type': 'application/json',
};
if (response.statusMessage) {
res.writeHead(response.status, response.statusMessage, headers);
} else {
res.writeHead(response.status, headers);
}
if (response.type === 'result') {
res.end(JSON.stringify(response.body));
}
if (response.type === 'error') {
res.end(JSON.stringify(response.error));
}
} else {
res.writeHead(404);
res.end();
}
} catch (error) {
res.writeHead(500);
res.end(JSON.stringify(error));
}
});
```
Another example with builtin express-like frameworks support
```js
import { useSofa } from 'sofa-api';

@@ -30,2 +78,3 @@ import express from 'express';

useSofa({
basePath: '/api',
schema,

@@ -99,2 +148,3 @@ })

useSofa({
basePath: '/api',
schema,

@@ -121,2 +171,3 @@ async context({ req }) {

useSofa({
basePath: '/api',
schema,

@@ -160,2 +211,3 @@ ignore: ['Message.author'],

useSofa({
basePath: '/api',
schema,

@@ -175,2 +227,3 @@ depthLimit: 2,

useSofa({
basePath: '/api',
schema,

@@ -268,2 +321,3 @@ async execute(args) {

useSofa({
basePath: '/api',
schema,

@@ -312,2 +366,3 @@ onRoute(info) {

useSofa({
basePath: '/api',
schema,

@@ -314,0 +369,0 @@ onRoute(info) {

import { GraphQLSchema } from 'graphql';
import { Ignore, Context, ContextFn, ExecuteFn, OnRoute, MethodMap } from './types';
import { Ignore, ExecuteFn, OnRoute, MethodMap } from './types';
import { ErrorHandler } from './express';
export interface SofaConfig {
basePath: string;
schema: GraphQLSchema;
context?: Context;
execute?: ExecuteFn;

@@ -23,4 +23,4 @@ /**

export interface Sofa {
basePath: string;
schema: GraphQLSchema;
context: Context;
models: string[];

@@ -35,2 +35,1 @@ ignore: Ignore;

export declare function createSofa(config: SofaConfig): Sofa;
export declare function isContextFn(context: any): context is ContextFn;
import { ExecutionResult } from 'graphql';
import { Sofa } from './sofa';
import type { ContextValue } from './types';
import type { Sofa } from './sofa';
export declare type ID = string;

@@ -22,6 +23,3 @@ export declare type SubscriptionFieldName = string;

constructor(sofa: Sofa);
start(event: StartSubscriptionEvent, { req, res, }: {
req: any;
res: any;
}): Promise<ExecutionResult<{
start(event: StartSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<{
[key: string]: any;

@@ -34,6 +32,3 @@ }, {

stop(id: ID): Promise<StopSubscriptionResponse>;
update(event: UpdateSubscriptionEvent, { req, res, }: {
req: any;
res: any;
}): Promise<ExecutionResult<{
update(event: UpdateSubscriptionEvent, contextValue: ContextValue): Promise<ExecutionResult<{
[key: string]: any;

@@ -40,0 +35,0 @@ }, {

import { GraphQLArgs, ExecutionResult, DocumentNode } from 'graphql';
export declare type ContextValue = Record<string, any>;
export declare type ContextFn = (init: {
req: any;
res: any;
}) => ContextValue;
export declare type Context = ContextValue | ContextFn;
export declare type Ignore = string[];

@@ -9,0 +4,0 @@ export declare type ExecuteFn = (args: GraphQLArgs) => Promise<ExecutionResult<any>>;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc