Skip to content

Instantly share code, notes, and snippets.

@joduplessis
Created August 16, 2023 06:39
Show Gist options
  • Save joduplessis/6e159dd41464b9b30b8160b5a626b3de to your computer and use it in GitHub Desktop.
Save joduplessis/6e159dd41464b9b30b8160b5a626b3de to your computer and use it in GitHub Desktop.
Fastify plugin that generates React hooks & service methods from a config object in the header.
const workspacesReact = () => {
const [error, setError] = useState(null)
const [loading, setLoading] = useState(null)
const params = useParams()
const { some, shit } = location
const [name, setName] = useState('')
const handleWorkspacesPost = async () => {
setLoading(true)
try {
const { workspaceId } = params
const { workspace } = await workspacesPost(workspaceId, { name, image }, { some, shit })
setWorkspace(workspace)
setLoading(false)
} catch (e) {
setLoading(false)
setError(e)
}
}
return null
}
const API_HOST = ''
const getAuthToken = () => null
const createQueryVariables = (obj) => ''
export const workspacesPost = async (workspaceId, { name, image }, { some, shit }) => {
try {
const token = getAuthToken()
const url = API_HOST + `/workspaces/${workspaceId}?` + createQueryVariables({ some, shit })
const result: any = await fetch(url, {
body: JSON.stringify({ name, image }),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
},
})
if (result.headers.get('content-type') == 'application/json') {
return await result.json()
} else {
return result
}
} catch (error) {
throw error
}
}
export default async (fastify, opts) => {
fastify.post('/workspaces',
{
web: {
group: 'workspaces',
name: 'post',
body: 'name, image',
query: 'some, shit',
state: name,
result: workspace,
},
},
async (request, reply) => {
try {
reply.code(200).send({ success: true ))
} catch (error) {
reply.code(500).send(error)
}
}
)
}
require('dotenv').config()
import fp from 'fastify-plugin'
import fs from 'fs'
const toSentenceCase = function (str: string) {
return str
.split(' ')
.map((s) => s.toLowerCase())
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
.join(' ')
}
const prettier = require("prettier")
const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1)
const generateWebPathFromRoutePath = (routePath: string) => {
const parts = routePath.slice(1).split('/')
const urlParts = []
const argParts = []
parts.map((part) => {
if (part.charAt(0) == ':') {
const cleanedPart = part.slice(1)
urlParts.push('${' + cleanedPart + '}')
argParts.push(cleanedPart)
} else {
urlParts.push(part)
}
})
return {
url: urlParts.join('/'),
arg: argParts.join(', '),
}
}
if (process.env.NODE_ENV !== 'production') {
fs.writeFileSync(
'web-services.ts',
`
const API_HOST = ''
const getAuthToken = () => null
const createQueryVariables = (obj) => ''
`
)
fs.writeFileSync(
'web-react.ts',
`
const useEffect = (a, b) => null
const useState = (a) => [a, (e: any) => console.log(e)]
const props: any = { children: null }
const body: any = {}
const query: any = {}
const other: any = {}
const location: any = {}
const joinRoom = (a) => null
const useRoom = (a, b) => null
const useParams: any = () => {}
const useConnection: any = (a) => {}
`
)
}
let reactFunctions: any = {}
const reactImports = []
const reactPath = 'web-react.ts'
const servicesPath = 'web-services.ts'
const prettyConfig = {
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"quoteProps": "consistent"
}
module.exports = fp(async function (fastify, options) {
fastify.addHook('onRoute', (routeOptions: any) => {
if (routeOptions.routePath !== '' && routeOptions.routePath !== '/*' && routeOptions.method !== 'HEAD' && process.env.NODE_ENV !== 'production') {
const { preValidation, method, url, path, handler, onSend, routePath, prefix, logLevel, attachValidation, web } = routeOptions
if (!web) return
const { group, name, body, query, state, result } = web
const webRoutePath = generateWebPathFromRoutePath(routePath)
const args = (() => {
const parts = []
if (webRoutePath.arg) parts.push(`${webRoutePath.arg}`)
if (body) parts.push(`{ ${body} }`)
if (query) parts.push(`{ ${query} }`)
return parts.join(', ')
})()
const urlPath = query
? `API_HOST + '/${webRoutePath.url}?' + createQueryVariables({ ${query} })`
: `API_HOST + '/${webRoutePath.url}'`
const functionName = `${group}${toSentenceCase(name).split(' ').join('')}`
const functionNameReact = `${functionName}React`
const functionNameRequest = `handle${capitalizeFirstLetter(functionName)}`
reactImports.push(functionName)
// -------------------------------------------
const servicesDeclaration = `
export const ${functionName} = async (${args}) => {
try {
const token = getAuthToken()
const url = ${urlPath.split("'").join('`')}
const result: any = await fetch(url, {
body: ${body ? 'JSON.stringify({ ' + body + ' })' : 'null'},
method: '${method}',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
})
if (result.headers.get('content-type') == 'application/json') {
return await result.json()
} else {
return result
}
} catch (error) {
throw error
}
}
`
fs.appendFileSync(servicesPath, servicesDeclaration)
// -------------------------------------------
if (!reactFunctions[group]) reactFunctions[group] = { state: [], functions: [], query: [] }
const functionDeclaration = `
const ${functionNameRequest} = async () => {
setLoading(true)
try {
${webRoutePath.arg ? `const { ${webRoutePath.arg} } = params` : ``}
const ${result ? `{ ${result} }` : 'result'} = await ${functionName}(${args})
${result ? result
.split(',')
.map(r => r.trim())
.map((r) => `set${capitalizeFirstLetter(r)}(${r})`)
.join('\n') : ''}
setLoading(false)
} catch (e) {
setLoading(false)
setError(e)
}
}
`
reactFunctions[group].state = [...reactFunctions[group].state, ...(state || '').split(',').map(s => s.trim()).filter(s => !!s) ]
reactFunctions[group].query = [...reactFunctions[group].query, ...(query || '').split(',').map(q => q.trim()).filter(q => !!q) ]
reactFunctions[group].functions = [...reactFunctions[group].functions, functionDeclaration ]
}
})
fastify.addHook('onReady', function (done) {
const reactGroups = Object.keys(reactFunctions).map(groupName => {
const { query, state, functions } = reactFunctions[groupName]
return `
const ${groupName}React = () => {
const [error, setError] = useState(null)
const [loading, setLoading] = useState(null)
const params = useParams()
${query ? `const { ${Array.from(new Set(query)).join(', ')} } = location` : ``}
${state ? Array.from(new Set(state))
.map((s) => `const [${s}, set${capitalizeFirstLetter(s)}] = useState('')`)
.join('\n') : ''}
${functions.map(f => f).join('\n')}
useConnection((state) => {
switch (state) {
case 'offline': return
case 'online': return
case 'restored': return // get() up to date data
}
})
useRoom('${groupName}Id', ({ data, action }) => {
// this does a joinRoom('${groupName}Id') too
// action = UPDATE/DELETE/ETC
// const { name, image } = data
// setName(name)
// setImage(image)
})
return null
}
`
}).join('\n')
fs.appendFileSync(reactPath, `\nimport { ${reactImports.join(', ')} } from './web-services' \n`)
fs.appendFileSync(reactPath, reactGroups)
const reactText = fs.readFileSync(reactPath, 'utf-8')
const formattedReactText = prettier.format(reactText, prettyConfig)
fs.writeFileSync(reactPath, formattedReactText)
const servicesText = fs.readFileSync(servicesPath, 'utf-8')
const formattedServicesText = prettier.format(servicesText, prettyConfig)
fs.writeFileSync(servicesPath, formattedServicesText)
const err = null
done(err)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment