Comparing version 0.9.1 to 1.0.0
@@ -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>; |
495
index.esm.js
@@ -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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
217720
11
2305
1
376
+ Addedtrouter@3.1.0
+ Added@graphql-tools/utils@7.2.5(transitive)
+ Addedregexparam@1.3.0(transitive)
+ Addedtrouter@3.1.0(transitive)
- Removediterall@1.3.0
- Removed@graphql-tools/utils@7.2.4(transitive)
- Removedaccepts@1.3.8(transitive)
- Removedarray-flatten@1.1.1(transitive)
- Removedbody-parser@1.20.3(transitive)
- Removedbytes@3.1.2(transitive)
- Removedcall-bind-apply-helpers@1.0.1(transitive)
- Removedcall-bound@1.0.3(transitive)
- Removedcontent-disposition@0.5.4(transitive)
- Removedcontent-type@1.0.5(transitive)
- Removedcookie@0.7.1(transitive)
- Removedcookie-signature@1.0.6(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddepd@2.0.0(transitive)
- Removeddestroy@1.2.0(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedee-first@1.1.1(transitive)
- Removedencodeurl@1.0.22.0.0(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.1.1(transitive)
- Removedescape-html@1.0.3(transitive)
- Removedetag@1.8.1(transitive)
- Removedexpress@4.21.2(transitive)
- Removedfinalhandler@1.3.1(transitive)
- Removedforwarded@0.2.0(transitive)
- Removedfresh@0.5.2(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.7(transitive)
- Removedget-proto@1.0.1(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhttp-errors@2.0.0(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedinherits@2.0.4(transitive)
- Removedipaddr.js@1.9.1(transitive)
- Removediterall@1.3.0(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedmedia-typer@0.3.0(transitive)
- Removedmerge-descriptors@1.0.3(transitive)
- Removedmethods@1.1.2(transitive)
- Removedmime@1.6.0(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedms@2.0.02.1.3(transitive)
- Removednegotiator@0.6.3(transitive)
- Removedobject-inspect@1.13.4(transitive)
- Removedon-finished@2.4.1(transitive)
- Removedparseurl@1.3.3(transitive)
- Removedpath-to-regexp@0.1.12(transitive)
- Removedproxy-addr@2.0.7(transitive)
- Removedqs@6.13.0(transitive)
- Removedrange-parser@1.2.1(transitive)
- Removedraw-body@2.5.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsend@0.19.0(transitive)
- Removedserve-static@1.16.2(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedside-channel@1.1.0(transitive)
- Removedside-channel-list@1.0.0(transitive)
- Removedside-channel-map@1.0.1(transitive)
- Removedside-channel-weakmap@1.0.2(transitive)
- Removedstatuses@2.0.1(transitive)
- Removedtoidentifier@1.0.1(transitive)
- Removedtype-is@1.6.18(transitive)
- Removedunpipe@1.0.0(transitive)
- Removedutils-merge@1.0.1(transitive)
- Removedvary@1.1.2(transitive)
Updated@graphql-tools/utils@7.2.5