From 68ca6637a0ca3b0b2b5d164a114bb914cc8cb0bf Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Wed, 2 Oct 2024 19:24:03 +0200 Subject: [PATCH 1/4] fix: some errors and warning coming from rollup build output --- src/directory.ts | 57 +++++++++++++++----------- src/index.ts | 102 +++++++++++++++++++--------------------------- src/routeUtils.ts | 86 +++++++++++++++++++------------------- src/types.ts | 45 +++++++++++++++++--- src/utils.ts | 20 +++++---- 5 files changed, 169 insertions(+), 141 deletions(-) diff --git a/src/directory.ts b/src/directory.ts index 66980db..348ea3c 100644 --- a/src/directory.ts +++ b/src/directory.ts @@ -27,10 +27,10 @@ export class Directory { path.endsWith('.metadata.json') || path.endsWith('.chain.js') ); + }, + ignore: { + // extensions: ['keys'] } - }, - ignore: { - // extensions: ['keys'] } }); } @@ -53,31 +53,42 @@ export class Directory { return this.files.map(({ path }) => path); } + private getEndpoint (name: string) { + const [path, ext, json] = name.split('.'); + if (ext === 'zen') { + return { + path: path, + contract: formatContract(this.getContent(name) || ''), + keys: this.getJSON(path, 'keys'), + conf: this.getContent(path + '.conf') || '', + schema: this.getJSONSchema(path), + metadata: newMetadata(this.getJSON(path, 'metadata') || {}) + }; + } else if (ext == 'chain' && json == 'js') { + return { + path: path, + chain: this.getContent(name) || '', + schema: this.getJSONSchema(path), + metadata: newMetadata(this.getJSON(path, 'metadata') || {}), + conf: '' + }; + } + return; + } + get files() { const result: Endpoints[] = []; this.liveDirectory.files.forEach((c, f) => { - const [path, ext, json] = f.split('.'); - if (ext === 'zen') { - result.push({ - path: path, - contract: formatContract(this.getContent(f)), - keys: this.getJSON(path, 'keys'), - conf: this.getContent(path + '.conf') || '', - schema: this.getJSONSchema(path), - metadata: newMetadata(this.getJSON(path, 'metadata') || {}) - }); - } else if (ext == 'chain' && json == 'js') { - result.push({ - path: path, - chain: this.getContent(f), - schema: this.getJSONSchema(path), - metadata: newMetadata(this.getJSON(path, 'metadata') || {}) - }); - } + const res = this.getEndpoint(f); + if (res) result.push(res); }); return result; } + public endpoint(path: string): Endpoints | undefined { + return this.getEndpoint(path); + } + private getJSON(path: string, type: 'schema' | 'keys' | 'metadata' | 'chain') { try { const k = this.getContent(`${path}.${type}.json`); @@ -103,11 +114,11 @@ export class Directory { this.liveDirectory.on(event, cb); } - public onAdd(cb: (path: string, file: LiveFile) => void) { + public onAdd(cb: (path: string) => void) { this.onChange('add', cb); } - public onUpdate(cb: (path: string, file: LiveFile) => void) { + public onUpdate(cb: (path: string) => void) { this.onChange('update', cb); } diff --git a/src/index.ts b/src/index.ts index edb2bb3..11ba1d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,11 +6,7 @@ import dotenv from 'dotenv'; import fs from 'fs'; import mime from 'mime'; import path from 'path'; -import { - TemplatedApp, - us_socket_local_port, - LIBUS_LISTEN_EXCLUSIVE_PORT -} from 'uWebSockets.js'; +import { TemplatedApp, us_socket_local_port } from 'uWebSockets.js'; import { autorunContracts } from './autorun.js'; import { config } from './cli.js'; import { Directory } from './directory.js'; @@ -24,10 +20,10 @@ import { openapiTemplate } from './openapi.js'; import { SlangroomManager } from './slangroom.js'; -import { formatContract } from './fileUtils.js'; import { getSchema, getQueryParams, prettyChain, newMetadata } from './utils.js'; import { forbidden, notFound, unprocessableEntity, internalServerError } from './responseUtils.js'; import { createAppWithBasePath, generateRoute, runPrecondition } from './routeUtils.js'; +import { Endpoints, Events } from './types.js'; dotenv.config(); @@ -37,7 +33,7 @@ const Dir = Directory.getInstance(); const PROM = process.env.PROM == 'true'; if (typeof process.env.FILES_DIR == 'undefined') { - process.env.FILES_DIR = config.zencodeDir; + process.env.FILES_DIR = config.zencodeDirectory; } const setupProm = async (app: TemplatedApp) => { @@ -79,7 +75,7 @@ const ncrApp = async () => { if (!metadata.hidden && !metadata.hideFromOpenapi) acc.push(`http://${req.getHeader('host')}${config.basepath}${path}`); return acc; - }, []); + }, [] as string[]); res .writeStatus('200 OK') .writeHeader('Content-Type', 'application/json') @@ -90,17 +86,17 @@ const ncrApp = async () => { }) .get('/oas.json', async (res) => { definition.paths = {}; - const tags = []; + const tags: string[] = []; await Promise.all( Dir.files.map(async (endpoints) => { const { path, metadata } = endpoints; - if (metadata.tags) tags.push(...metadata.tags); - if (definition.paths && !metadata.hidden && !metadata.hideFromOpenapi) { - const prefixedPath = config.basepath + path; + if (metadata.tags) tags.push(...metadata.tags); + if (definition.paths && !metadata.hidden && !metadata.hideFromOpenapi) { + const prefixedPath = config.basepath + path; const schema = await getSchema(endpoints); if (schema) definition.paths[prefixedPath] = generatePath( - endpoints.contract ?? prettyChain(endpoints.chain), + 'contract' in endpoints ? endpoints.contract : prettyChain(endpoints.chain), schema, metadata ); @@ -109,12 +105,15 @@ const ncrApp = async () => { } }) ); - const customTags = tags.reduce((acc, tag) => { - if (tag === defaultTagsName.zen) return acc; - const t = { name: tag }; - if (!acc.includes(t)) acc.push(t); - return acc; - }, []); + const customTags = tags.reduce( + (acc, tag) => { + if (tag === defaultTagsName.zen) return acc; + const t = { name: tag }; + if (!acc.includes(t)) acc.push(t); + return acc; + }, + [] as { name: string }[] + ); definition.tags = [...customTags, ...defaultTags]; res.cork(() => { res @@ -167,7 +166,7 @@ const generatePublicDirectory = (app: TemplatedApp) => { res.writeStatus('500').end('Aborted'); }); let url = req.getUrl(); - if (url.split('/').pop().startsWith('.')) { + if (url.split('/').pop()?.startsWith('.')) { notFound(res, L, new Error('Try to access hidden file')); return; } @@ -182,7 +181,7 @@ const generatePublicDirectory = (app: TemplatedApp) => { if (fs.existsSync(file + '.metadata.json')) { let publicMetadata; try { - publicMetadata = JSON.parse(fs.readFileSync(file + '.metadata.json')); + publicMetadata = JSON.parse(fs.readFileSync(file + '.metadata.json').toString('utf-8')); } catch (e) { unprocessableEntity( res, @@ -207,7 +206,7 @@ const generatePublicDirectory = (app: TemplatedApp) => { .writeStatus('200 OK') .writeHeader('Access-Control-Allow-Origin', '*') .writeHeader('Content-Type', contentType) - .end(fs.readFileSync(file)); + .end(fs.readFileSync(file).toString('utf-8')); }); } else { notFound(res, L, new Error(`File not found: ${file}`)); @@ -216,27 +215,6 @@ const generatePublicDirectory = (app: TemplatedApp) => { } }; -const generateEndpoint = (basePath: string): Endpoints | undefined => { - if (Dir.getContent(basePath + '.zen') !== undefined) { - return { - path: basePath, - contract: formatContract(Dir.getContent(basePath + '.zen')), - keys: Dir.getJSON(basePath, 'keys'), - conf: Dir.getContent(basePath + '.conf') || '', - schema: Dir.getJSONSchema(basePath), - metadata: newMetadata(Dir.getJSON(basePath, 'metadata') || {}) - }; - } else if (Dir.getContent(basePath + '.chain.js') !== undefined) { - return { - path: basePath, - chain: Dir.getContent(basePath + '.chain.js'), - schema: Dir.getJSONSchema(basePath), - metadata: newMetadata(Dir.getJSON(basePath, 'metadata') || {}) - }; - } - return; -}; - Dir.ready(async () => { const app = await ncrApp(); @@ -244,16 +222,18 @@ Dir.ready(async () => { await Promise.all( Dir.files.map(async (endpoint) => { - await generateRoute(app, endpoint, 'add'); + await generateRoute(app, endpoint, Events.Add); }) ); generatePublicDirectory(app); - - app.listen(config.port, LIBUS_LISTEN_EXCLUSIVE_PORT, (socket) => { + const listenOption = 1; + app.listen(config.port, listenOption, (socket) => { if (socket) { const port = us_socket_local_port(socket); - L.info(`Swagger UI is running on http://${config.hostname}:${port}${config.basepath}${config.openapiPath}`); + L.info( + `Swagger UI is running on http://${config.hostname}:${port}${config.basepath}${config.openapiPath}` + ); } else { L.error('Port already in use ' + config.port); throw new Error('Port already in use ' + config.port); @@ -262,39 +242,39 @@ Dir.ready(async () => { Dir.onAdd(async (path: string) => { const [baseName, ext, json] = path.split('.'); - const endpoint = generateEndpoint(baseName); + const endpoint = Dir.endpoint(baseName); if (!endpoint) return; - let event: string; + let event: Events; if (ext === 'zen' || (ext === 'chain' && json === 'js')) { - event = 'add'; + event = Events.Add; } else { - event = 'update'; + event = Events.Update; } await generateRoute(app, endpoint, event); }); Dir.onUpdate(async (path: string) => { - const endpoint = generateEndpoint(path.split('.')[0]); + const endpoint = Dir.endpoint(path.split('.')[0]); if (!endpoint) return; - await generateRoute(app, endpoint, 'update'); + await generateRoute(app, endpoint, Events.Update); }); Dir.onDelete(async (path: string) => { const [baseName, ext, json] = path.split('.'); - let endpoint: Endpoints; - let event: string; + let endpoint: Endpoints | undefined; + let event: Events; if (ext === 'zen' || (ext === 'chain' && json === 'js')) { endpoint = { path: baseName, - contract: null, - chain: null, + contract: '', + chain: '', conf: '', - metadata: {} + metadata: newMetadata({}) }; - event = 'delete'; + event = Events.Delete; } else { - endpoint = generateEndpoint(baseName); - event = 'update'; + endpoint = Dir.endpoint(baseName); + event = Events.Update; } if (!endpoint) return; await generateRoute(app, endpoint, event); diff --git a/src/routeUtils.ts b/src/routeUtils.ts index e050b82..4c61054 100644 --- a/src/routeUtils.ts +++ b/src/routeUtils.ts @@ -4,12 +4,12 @@ import fs from 'fs'; import _ from 'lodash'; -import { IMeta } from 'tslog'; +import { IMeta, Logger, type ILogObj } from 'tslog'; import { App, HttpResponse, TemplatedApp } from 'uWebSockets.js'; import { execute as slangroomChainExecute } from '@dyne/slangroom-chain'; import { reportZenroomError } from './error.js'; -import { Endpoints, JSONSchema, Logger, ILogObj } from './types.js'; +import { Endpoints, JSONSchema, Events } from './types.js'; import { config } from './cli.js'; import { SlangroomManager } from './slangroom.js'; import { forbidden, methodNotAllowed, notFound, unprocessableEntity } from './responseUtils.js'; @@ -19,17 +19,17 @@ import { template as proctoroom } from './applets.js'; // type MethodNames = - | 'get' - | 'post' - | 'options' - | 'del' - | 'patch' - | 'put' - | 'head' - | 'connect' - | 'trace' - | 'any' - | 'ws'; + | 'get' + | 'post' + | 'options' + | 'del' + | 'patch' + | 'put' + | 'head' + | 'connect' + | 'trace' + | 'any' + | 'ws'; export const createAppWithBasePath = (basepath: string): TemplatedApp => { const app = App(); @@ -56,8 +56,6 @@ export const createAppWithBasePath = (basepath: string): TemplatedApp => { listen: (...args: any[]) => wrapMethod('listen')(...args), listen_unix: (...args: any[]) => wrapMethod('listen_unix')(...args), - publish: (...args: any[]) => wrapMethod('publish')(...args), - numSubscribers: (...args: any[]) => wrapMethod('numSubscribers')(...args), addServerName: (...args: any[]) => wrapMethod('addServerName')(...args), domain: (...args: any[]) => wrapMethod('domain')(...args), removeServerName: (...args: any[]) => wrapMethod('removeServerName')(...args), @@ -76,11 +74,11 @@ export const createAppWithBasePath = (basepath: string): TemplatedApp => { connect: wrapPatternMethod('connect'), trace: wrapPatternMethod('trace'), any: wrapPatternMethod('any'), - ws: wrapPatternMethod('ws'), - }; + ws: wrapPatternMethod('ws') + }; return wrappedApp; -} +}; // @@ -100,9 +98,9 @@ const setCorsHeaders = (res: HttpResponse) => { export const runPrecondition = async (preconditionPath: string, data: Record) => { const s = SlangroomManager.getInstance(); - const zen = fs.readFileSync(preconditionPath + '.slang').toString(); + const zen = fs.readFileSync(preconditionPath + '.slang').toString('utf-8'); const keys = fs.existsSync(preconditionPath + '.keys.json') - ? JSON.parse(fs.readFileSync(preconditionPath + '.keys.json')) + ? JSON.parse(fs.readFileSync(preconditionPath + '.keys.json').toString('utf-8')) : null; await s.execute(zen, { data, keys }); }; @@ -110,7 +108,7 @@ export const runPrecondition = async (preconditionPath: string, data: Record, + data: Record, headers: Record>, schema: JSONSchema, LOG: Logger @@ -120,11 +118,11 @@ const execZencodeAndReply = async ( res.cork(() => res.writeStatus('400').end()); return; }); - const { contract, chain, keys, conf, metadata } = endpoint; + const { keys, conf, metadata } = endpoint; try { if (metadata.httpHeaders) { try { - if (data['http_headers'] !== undefined) { + if ('http_headers' in data) { throw new Error('Name clash on input key http_headers'); } data['http_headers'] = headers; @@ -149,21 +147,21 @@ const execZencodeAndReply = async ( return; } - let jsonResult: Record; + let jsonResult: Record = {}; try { - if (chain) { + if ('chain' in endpoint) { const dataFormatted = data ? JSON.stringify(data) : undefined; - const parsedChain = eval(chain)(); + const parsedChain = eval(endpoint.chain)(); const res = await slangroomChainExecute(parsedChain, dataFormatted); jsonResult = JSON.parse(res); } else { const s = SlangroomManager.getInstance(); - ({ result: jsonResult } = await s.execute(contract, { keys, data, conf })); + ({ result: jsonResult } = await s.execute(endpoint.contract, { keys, data, conf })); } } catch (e) { if (!res.aborted) { res.cork(() => { - const report = reportZenroomError(e as Error, LOG, endpoint); + const report = reportZenroomError(e as Error, LOG); res .writeStatus(metadata.errorCode) .writeHeader('Access-Control-Allow-Origin', '*') @@ -173,7 +171,7 @@ const execZencodeAndReply = async ( } } if (metadata.httpHeaders) { - if (jsonResult.http_headers !== undefined && jsonResult.http_headers.response !== undefined) { + if (jsonResult.http_headers?.response !== undefined) { headers.response = jsonResult.http_headers.response; } delete jsonResult.http_headers; @@ -209,7 +207,7 @@ const generatePost = ( endpoint: Endpoints, schema: JSONSchema, LOG: Logger, - action: 'add' | 'update' | 'delete' + action: Events ) => { const { path, metadata } = endpoint; app.post(path, (res, req) => { @@ -239,7 +237,9 @@ const generatePost = ( if (isLast) { let data; try { - data = JSON.parse(buffer ? Buffer.concat([buffer, chunk]) : chunk); + data = JSON.parse( + buffer ? Buffer.concat([buffer, chunk]).toString('utf-8') : chunk.toString('utf-8') + ); } catch (e) { L.error(e); res @@ -266,7 +266,7 @@ const generateGet = ( endpoint: Endpoints, schema: JSONSchema, LOG: Logger, - action: 'add' | 'update' | 'delete' + action: Events ) => { const { path, metadata } = endpoint; app.get(path, async (res, req) => { @@ -302,15 +302,17 @@ const generateGet = ( }); }; -export const generateRoute = async ( - app: TemplatedApp, - endpoint: Endpoints, - action: 'add' | 'update' | 'delete' -) => { - const { contract, path, metadata } = endpoint; +export const generateRoute = async (app: TemplatedApp, endpoint: Endpoints, action: Events) => { + const { path, metadata } = endpoint; if (metadata.hidden) return; + let raw: string; + if ('contract' in endpoint) { + raw = endpoint.contract; + } else { + raw = endpoint.chain; + } - let schema = null; + let schema; if (action !== 'delete') { schema = await getSchema(endpoint); if (!schema) { @@ -350,7 +352,7 @@ export const generateRoute = async ( notFound(res, LOG, new Error(`Not found on ${path}/raw`)); return; } - res.writeStatus('200 OK').writeHeader('Content-Type', 'text/plain').end(contract); + res.writeStatus('200 OK').writeHeader('Content-Type', 'text/plain').end(raw); }); app.get(path + '/app', async (res) => { @@ -359,10 +361,10 @@ export const generateRoute = async ( return; } const result = _.template(proctoroom)({ - contract: contract, + contract: raw, schema: JSON.stringify(schema), title: path || 'Welcome 🥳 to ', - description: contract, + description: raw, endpoint: `${config.basepath}${path}` }); diff --git a/src/types.ts b/src/types.ts index d8ec05d..799daa7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,15 +27,16 @@ export interface Config { basepath: string; } -export interface Endpoints { +export type Endpoints = { path: string; - contract?: string | undefined; - chain?: JSON | undefined; - keys?: JSON | undefined; + keys?: JSON; conf: string; - schema?: JSONSchema | undefined; + schema?: JSONSchema; metadata: Metadata; -} +} & ( + | { contract: string } + | { chain: string } +) interface CodecAttr { encoding: 'string' | 'float'; @@ -66,4 +67,36 @@ export interface Metadata { examples: {}; tags: Array; + + precondition?: string } + +export interface MetadataRaw { + hidden?: boolean; + hide_from_openapi?: boolean; + disable_get?: boolean; + disable_post?: boolean; + + content_type?: RecognizedString; + http_headers?: boolean; + + success_code?: RecognizedString; + success_content_type?: RecognizedString; + success_description?: RecognizedString; + + error_code?: RecognizedString; + error_description?: RecognizedString; + error_content_type?: RecognizedString; + + examples?: {}; + tags?: Array; + + precondition?: string +} + + +export enum Events { + Add = "add", + Update = "update", + Delete = "delete" +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 353588a..1b77055 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,17 +9,18 @@ import betterAjvErrors from 'better-ajv-errors'; import _ from 'lodash'; import { introspect } from 'zenroom'; import { config } from './cli.js'; -import { Codec, Endpoints, JSONSchema, Metadata } from './types'; +import { Codec, Endpoints, JSONSchema, Metadata, MetadataRaw } from './types'; import { defaultTagsName } from './openapi.js'; +import { HttpRequest } from 'uWebSockets.js'; const L = config.logger; // export async function getSchema(endpoints: Endpoints): Promise { - const { contract, chain, keys } = endpoints; + const { keys } = endpoints; let schema = endpoints.schema; - if (typeof chain !== 'undefined') return schema; - schema = schema ?? (await getSchemaFromIntrospection(contract)); + if ( 'chain' in endpoints ) return schema; + schema = schema ?? (await getSchemaFromIntrospection(endpoints.contract)); if (!keys) return schema; else if (schema) return removeKeysFromSchema(schema, keys); } @@ -79,7 +80,8 @@ export const validateData = (schema: JSONSchema, data: JSON | Record { +export const newMetadata = (configRaw: MetadataRaw): Metadata => { return { httpHeaders: configRaw['http_headers'] || false, errorCode: configRaw['error_code'] || '500', @@ -128,7 +130,7 @@ export const newMetadata = (configRaw: JSON): Metadata => { tags: configRaw['tags'] || [defaultTagsName.zen], hidden: configRaw['hidden'] || false, hideFromOpenapi: configRaw['hide_from_openapi'] || false, - precondition: configRaw['precondition'] || false + precondition: configRaw['precondition'] }; }; @@ -138,11 +140,11 @@ function createAjv(): Ajv { return ajv; } -export const getQueryParams = (req): Record => { +export const getQueryParams = (req: HttpRequest): Record => { const data: Record = {}; const q = req.getQuery(); if (q) { - q.split('&').map((r) => { + q.split('&').map((r: string) => { const [k, v] = r.split('='); data[k] = v; }); From 6897f002f3531cee1c5eb3d77568e5308d11efcb Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Wed, 2 Oct 2024 19:39:06 +0200 Subject: [PATCH 2/4] fix: do not set FILES_DIR if not already set --- src/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 11ba1d5..d953fe7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,10 +32,6 @@ const Dir = Directory.getInstance(); const PROM = process.env.PROM == 'true'; -if (typeof process.env.FILES_DIR == 'undefined') { - process.env.FILES_DIR = config.zencodeDirectory; -} - const setupProm = async (app: TemplatedApp) => { const client = await import('prom-client'); const register = new client.Registry(); From d3f6faf99457428ab4b86fbde5b9136cba0e1efb Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 3 Oct 2024 10:34:31 +0200 Subject: [PATCH 3/4] fix: error reporting on data validation --- src/utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 1b77055..af88f36 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,7 +19,7 @@ const L = config.logger; export async function getSchema(endpoints: Endpoints): Promise { const { keys } = endpoints; let schema = endpoints.schema; - if ( 'chain' in endpoints ) return schema; + if ('chain' in endpoints) return schema; schema = schema ?? (await getSchemaFromIntrospection(endpoints.contract)); if (!keys) return schema; else if (schema) return removeKeysFromSchema(schema, keys); @@ -81,7 +81,12 @@ export const validateData = (schema: JSONSchema, data: JSON | Record Date: Thu, 3 Oct 2024 11:46:41 +0200 Subject: [PATCH 4/4] chores: more minor fixes --- src/cli.ts | 4 ++-- src/index.ts | 3 ++- src/routeUtils.ts | 13 ++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index cee0dc4..e7388f4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,11 +5,11 @@ import { Command, Option } from 'commander'; import 'dotenv/config'; import { statSync } from 'node:fs'; -import { Logger } from 'tslog'; +import { Logger, type ILogObj } from 'tslog'; import p from '../package.json' with { type: 'json' }; import { Config } from './types'; export const program = new Command(); -const L = new Logger({ +const L: Logger = new Logger({ name: p.name, type: 'pretty', prettyLogTemplate: diff --git a/src/index.ts b/src/index.ts index d953fe7..731fd12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,7 +48,8 @@ const setupProm = async (app: TemplatedApp) => { help: 'Emissions for 1GB', collect() { const emissions = swd.perByte(1000000000); - this.set(emissions); + const emissionValue = typeof emissions === 'number' ? emissions : emissions.total; + this.set(emissionValue); } }); diff --git a/src/routeUtils.ts b/src/routeUtils.ts index 4c61054..4c31568 100644 --- a/src/routeUtils.ts +++ b/src/routeUtils.ts @@ -147,7 +147,9 @@ const execZencodeAndReply = async ( return; } - let jsonResult: Record = {}; + let jsonResult: { + http_headers?: { response?: Record } + } & Record = {}; try { if ('chain' in endpoint) { const dataFormatted = data ? JSON.stringify(data) : undefined; @@ -312,12 +314,13 @@ export const generateRoute = async (app: TemplatedApp, endpoint: Endpoints, acti raw = endpoint.chain; } - let schema; + let schema: JSONSchema = { type: 'object', properties: {} }; if (action !== 'delete') { - schema = await getSchema(endpoint); - if (!schema) { + const s = await getSchema(endpoint); + if (!s) { L.warn(`🛟 ${path} 🚧 Please provide a valide schema`); - schema = { type: 'object', properties: {} }; + } else { + schema = s; } }