@imposterlabs/project
mock APIs, intelligently, with context, and perform other stuff as well
😄
Setting up
- Clone the project
git clone https://github.com/YashKumarVerma/maya-mvp
andcd maya-mvp
to open it - Instal the dependencies with
npm i
- Start development server with
nodemon
Triggers
Triggers are like functions, which are called by other triggers or routes. The main aim is to perform database operations / network calls before/after the route has been executed. Use them to run tasks after something happened.
Example usage
- Banking
- you make a request to the bank to collect money via UPI, bank returns OK, accepted, and starts processing.
- other user can take upto 15 minutes to accept/decline the UPI request
- bank send back a webhook to your server whether the operation was successful or not.
- the webhook is a
trigger
, which runsafter
yourroute
sendsOK
Anatomy of triggers
interface IMayaTriggerDefinition {
// environment configuration of the http processor
// added by processor, user DOES NOT pass this
environment?: IEnvironment,
// object containing prompts to make to user when trigger being processed
// if autonomous is true, default values are used
// passed by user
prompt?: {
name: "Yash Kumar Verma",
age: 21
}
// unique identifier for the trigger, used to call other triggers
// passed by user
name: string | ContextualFunction<IContext, string>
// one of the HTTP method
// passed by user
method: Method
// URL for the HTTP request, or function which returns a string
// passed by user
url: string | ContextualFunction<IContext, string>
// header for HTTP request, record<String, string> or function which returns the same
// passed by user
header?: IHeader | ContextualFunction<IContext, IHeader>
// http body sent as payload, can be a contextual function which returns an object
// passed by user
body?: any | ContextualFunction<IContext, any>
// function to run on data returned after HTTP request
// passed by user
response?: (
// instance of environment configurations
environment: {},
// data collected from user prompt, or defaults if autonomous is true
prompt: {},
// full configuration of the trigger object
trigger: {},
// complete HTTP response object
response: {}
) => {}
// names of triggers to run before and after current trigger
// passed by user
before?: Array<string>
after?: Array<string>
// setting true will force default values for prompt variables
// passed by user
autonomous?: boolean
}
properties which have an asterisk *
mean that they're optional
Routes
Routes are what routes are. Set a method, a url, and a response handler. Each response handler has context about the application (check interfaces or src/server.ts
for examples). Also supports faker.js
as generator of fake data.
State
An important part of writing a responsive mock server is state management. A total of three state handlers are provided by the application.
StateHandler
import { StateHandler } from "./state/StateHandler"
const { get: getSimpleState, set: setSimpleState } = StateHandler<string>("default_value")
- the data is kept in memory, not on database.
- fastest of the three options.
- strong support for custom data types.
PersistentStateHandler
import { PersistentStateHandler } from "./state/PersistentStateHandler"
const { get: getPersistentState, set: setPersistentState } = await PersistentStateHandler("default_value")
- the data kept in memory as well as database.
- slower than
StateHandler
, but persists the data in the database. -
get
andset
are twin functions, which operate on the same data source.
Orphan
import { RetrieveOrphan, SaveAsOrphan } from "./state/Orphan"
await SaveAsOrphan(key, value)
const data = await RetrieveOrphan(key)
- the data is kept in database only.
- slower than others.
-
main advantage :
get
andset
are completely independent functions, useful when you want to define the object maps across different files or love a clean architecture.
Sample File
const { get: getSimpleState, set: setSimpleState } = StateHandler<string>("default_value")
const { get: getPersistentState, set: setPersistentState } = await PersistentStateHandler("default_value")
const triggerMaps: Array<IMayaTriggerDefinition> = [
{
name: "TRIGGER_BEFORE_1",
method: METHOD.GET,
url: "https://webhook.site/b0ae265d-839f-45fa-9774-c2d8bed11ddb",
response: async () => {
console.log("TRIGGER_BEFORE")
}
},
{
name: "TRIGGER_BEFORE_2",
method: METHOD.POST,
url: "https://webhook.site/b0ae265d-839f-45fa-9774-c2d8bed11ddb",
response: async () => {
console.log("TRIGGER_BEFORE")
},
body: {
"name": "TRIGGER_BEFORE_2"
}
},
{
name: "TRIGGER_AFTER_1",
method: METHOD.GET,
url: "https://webhook.site/b0ae265d-839f-45fa-9774-c2d8bed11ddb",
response: async () => {
console.log("TRIGGER_AFTER")
}
},
{
name: "TRIGGER_AFTER_2",
method: METHOD.POST,
url: "https://webhook.site/b0ae265d-839f-45fa-9774-c2d8bed11ddb",
response: async () => {
console.log("TRIGGER_AFTER")
},
body: {
"name": "TRIGGER_AFTER_2"
}
},
]
const routeMaps: Array<IMayaRouteDefinition> = [
{
method: METHOD.GET,
url: "/faker/:times",
response: ({ faker, request: { params } }) => {
const number = parseInt(params.times)
const generatorFunction = () => ({
name: faker.name.findName(),
organization: faker.company.companyName(),
dateJoined: faker.date.past(10),
image: faker.image.people()
})
const data = repeat(generatorFunction, number)
return data
},
before: ["TRIGGER_BEFORE_1", "TRIGGER_BEFORE_2"],
after: ["TRIGGER_AFTER_1", "TRIGGER_AFTER_2"]
},
{
method: METHOD.GET,
url: "/state-handler",
response: async () => {
const data = getSimpleState()
return { payload: data }
}
},
{
method: METHOD.POST,
url: "/state-handler",
response: async ({ request }) => {
const value = request.body.value
setSimpleState(value)
return { payload: value }
}
},
{
method: METHOD.GET,
url: "/persistent-state-handler",
response: async () => {
const data = await getPersistentState()
return { payload: data }
}
},
{
method: METHOD.POST,
url: "/persistent-state-handler",
response: async ({ request }) => {
const value = request.body.value
await setPersistentState(value)
return { payload: value }
}
},
{
method: METHOD.GET,
url: "/orphan-state-handler/:key",
response: async ({ request }) => {
const key = request.params.key
const data = await RetrieveOrphan(key)
return { payload: data }
}
},
{
method: METHOD.POST,
url: "/orphan-state-handler/:key",
response: async ({ request }) => {
const key = request.params.key
const value = request.body.value
await SaveAsOrphan(key, value)
return { payload: value }
}
}
]
Testing
It is recommended to use a service like webhook.site to inspect the requests.