Expose a programatic API
tamj0rd2 opened this issue · 3 comments
It'd be cool if JS could be evaluated to create fixtures from the export of a javascript file.
I could even write a programmatic version of ncdc. You'd pass it some io-ts (or similar) functions rather than just a type name.
That has a couple benefits
- not having to invoke the typescript compiler (ideally someone would just run ncdc using io-ts) which would speed everything up
- defined type guards can be reused at runtime for whatever purposes people may have
- whoever uses the programmatic version will probably be using typescript which means that people would be able to have dynamic responses
Mysteries:
- How would dynamic fixtures get reloaded? (maybe I can check how knex are doing this for their ts migration files)
- How would JSDOC be supported? Does io-ts support it?
Or even something like this. Lets say a user has a ncdc.ts
file in their project. The below kinda mirrors what we have in the docs:
interface Book {
ISBN: string
ISBN_13: string
author: string
title: string
inventoryId: string
}
const isBook = (responseBody: unknown): asserts responseBody is Book => {
if (typeof responseBody !== 'object') {
throw new Error('Expected val to be of type string')
}
// more validation logic would go here
}
const config = {
services: {
bookService: {
port: 3000,
realUrl: 'https://example.com',
tsconfig: './path/to/tsconfig.json',
rateLimit: 300,
resources: [
{
name: 'Book not found',
serveOnly: true,
request: {
method: 'GET',
endpoints: '/api/books/a-bad-id',
},
response: {
code: 404,
},
},
{
name: 'Book',
request: {
method: 'GET',
endpoints: ['/api/books/123', '/api/books/456'],
serveEndpoint: '/api/books/:id',
},
response: {
code: 200,
serveBody: {
ISBN: '9780141187761',
ISBN_13: '978-0141187761',
author: 'George Orwell',
title: ' 1984 Nineteen Eighty-Four',
inventoryId: 'item-87623',
},
// validator would replace the old "type" property
validator: isBook,
},
},
],
},
},
}
export default config
/**
* the new usage of the CLI would be:
* ncdc serve blah.ts --watch
* ncdc test blah.ts
*
* maybe there'd also be an option to choose the particular services, e.g
* ncdc serve blah.ts filmService --watch
* ncdc test blah.ts bookService
*/
Things this would change
- Because validation functions are passed as
type
, the typescript compiler wouldn't be needed anymore. Invoking the typescript compiler is what takes 99% of the time. - Those validation functions can be used elsewhere in peoples code - nice for runtime validation of http requests for example
- Using ts means someone can now have dynamic responses - really nice if someone wants to provide a function for
serveBody
- not having to use Concurrently to run 4 mocks APIs at the same time (basically, this fixes #184) - this would be a pretty great performance gain too, specifically because we won't have 4 instances of the typescript compiler watching files
- realUrl can now be provided via environment variables or any other way someone wants to
again, not sure how JSDOC would work after this- the answer is that it wouldn't. Users should provide their own validation functions that contain all of the validation logic required.- how do mocks get reloaded? Does someone need to restart ncdc every time they make a change? Maybe there would need to be a
watchFiles
glob array.
Things that stay the same
- user would still interact with ncdc with the CLI for testing, but an additional programmatic serve API could be made available too
- ncdc still needs access to (at least a subset of) the user's code
- the user still needs to have any installed any dependencies that are required from within the ncdc.ts file
- people can still import fixture files from disk if they want to
Taking a different, simpler approach:
Rather than using some serious magic, with an ncdc.ts, people can just write their own script that create an NCDC instance which exposes methods to create/test/generate etc.
Here's the same Books service example from the docs again, but also with a Films service that would be hosted on port 4001.
#!/usr/bin/env -S npx ts-node -P ./scripts/tsconfig.json ./scripts/fakes.ts
/* eslint-disable @typescript-eslint/no-var-requires */
import { resolve } from 'path'
import { NCDC, Method } from 'ncdc'
async function start(): Promise<void> {
const ncdc = new NCDC(
[
{
name: 'Books service',
port: 4000,
baseUrl: 'https://example.com',
resources: [
{
name: 'Book not found',
serveOnly: true,
request: {
method: Method.GET,
endpoints: ['/api/books/a-bad-id'],
},
response: {
code: 404,
},
},
{
name: 'Book',
serveOnly: false,
request: {
method: Method.GET,
body: undefined,
endpoints: ['/api/books/123', '/api/books/456'],
serveEndpoint: '/api/books/*',
},
response: {
code: 200,
headers: { 'content-type': 'application/json' },
type: 'Book',
serveBody: {
ISBN: '9780141187761',
ISBN_13: '978-0141187761',
author: 'George Orwell',
title: '1984 Nineteen Eighty-Four',
inventoryId: 'bitem-87623',
},
},
},
],
},
{
name: 'Films service',
port: 4001,
baseUrl: 'https://example.com',
resources: [
{
name: 'Serve error',
serveOnly: false,
request: {
endpoints: [],
serveEndpoint: '/api/*',
method: Method.GET,
},
response: {
code: 401,
body: 'nice meme, lol',
type: 'string',
},
},
],
},
],
{ tsconfigPath: getPath('./tsconfig.json') },
)
if (process.argv.includes('--serve')) {
await ncdc.serve({ watch: true })
} else if (process.argv.includes('--test')) {
await ncdc.test({})
} else if (process.argv.includes('--generate')) {
await ncdc.generate({ force: false, outputPath: getPath('json-schema') })
} else {
// a sensible-ish default if no flag is provided
await ncdc.serve({ watch: true })
}
return
}
void start().catch((err) => {
console.log('fatal error', err)
process.exit(1)
})
function getPath(pathRelativeToRepoRoot: string) {
return resolve(process.cwd(), pathRelativeToRepoRoot)
}
The string Book
still refers to the name of the type.
Things this would change
- Possibility in the future for dynamic responses - really nice if someone wants to provide a function for
serveBody
- not having to use Concurrently to run 4 mocks APIs at the same time (basically, this fixes #184) - this would be a pretty great performance gain too, specifically because we won't have 4 instances of the typescript compiler watching files
- realUrl can now be provided via environment variables or any other way someone wants to
- fixture files would not get reloaded. It would be up to the user to implement some watching logic for their files
- ncdc serve command would probably need to expose a start method (it already has stop)
Things that stay the same
- JSDOC would continue to work
- user can still use the CLI until it's deprecated
- ncdc still needs access to (at least a subset of) the user's code