lowcode-controller builds common Sequelize CRUD controller logic from a
declarative configuration object. It supports paginated, list, and single-item
queries, creation, updates, soft or physical deletion, validation, lifecycle
hooks, and custom response fields.
The package is written in strict TypeScript and publishes CommonJS JavaScript,
source maps, and generated type declarations. Existing JavaScript consumers
can continue to use require().
npm install lowcode-controllerimport path from 'node:path'
import {
LowCodeController,
type LowCodeControllerConfig,
} from 'lowcode-controller'
import CatModel from '../model/cat_model'
LowCodeController.setGlobalDBPath(path.join(__dirname, '../db/mysql'))
LowCodeController.setUserNameField('username')
const config: LowCodeControllerConfig = {
pageQuery: {
query: {
cat_name: { value: 'cat_name', like: true },
age: { type: 'constant', value: 2 },
},
extraOptions: {
order: [['cat_name', 'DESC']],
attributes: ['cat_name', 'age', 'job'],
},
},
getList: {
query: {
birthday: {
type: 'varTime',
value: ['start', 'end'],
},
},
afterQueryHook: async results =>
results.map(item => ({ ...item, description: 'cute cat' })),
},
create: {
validate: {
cat_name: { name: 'cat name', rule: 'required' },
},
constantCreate: { age: 2 },
enableBatch: true,
},
update: {
pick: ['job'],
afterUpdateHook: async updateItem => {
if (updateItem.job === 'rollback') {
throw new Error('roll back')
}
},
},
}
class CatsController extends LowCodeController {
public constructor() {
super(CatModel, config, null, {
creatorField: 'creator',
updatorField: 'updator',
})
}
}
export default new CatsController()JavaScript usage remains unchanged:
const { LowCodeController, utils } = require('lowcode-controller')Controller methods are bound to their instance and can be passed directly to a router.
const router = require('koa-router')()
const Cat = require('./controller/cats')
router.get('/api/cats', Cat.pageQuery)
router.get('/api/cats/list', Cat.getList)
router.get('/api/cats/:id', Cat.getSingle)
router.post('/api/cats', Cat.create)
router.put('/api/cats', Cat.update)
router.put('/api/cats/:id', Cat.update)
router.delete('/api/cats/:id', Cat.delete)Loads the module exporting the default Sequelize instance. Call it before constructing a controller.
Sets the context path used for creator and updater audit values. Nested paths
such as state.user.name are supported.
Changes the keys used by generated responses. The default response shape is:
{
"errno": 0,
"errmsg": "success",
"data": null
}new LowCodeController(model, config, db, options)| Parameter | Description |
|---|---|
model |
Sequelize model used by generated handlers |
config |
Per-handler configuration described below |
db |
Optional Sequelize instance; the global instance is the default |
options.creatorField |
Model field populated from the username path on create |
options.updatorField |
Model field populated on create, update, and soft delete |
options.defaultQuery |
Conditions merged into normal queries; default: { delete_flag: 1 } |
options.updateDelete |
Values written during soft delete; default: { delete_flag: 0 } |
Each key is a model field. value normally identifies the request field from
which the condition is read.
const query = {
cat_name: {
value: 'cat_name',
like: true,
require: true,
},
age: {
type: 'constant',
value: 2,
},
creator: {
type: 'constant',
ctxField: 'username',
},
color: {
type: 'varArray',
value: 'colors',
},
birthday: {
type: 'varTime',
value: ['startTime', 'endTime'],
},
tags: {
value: 'tag',
json: true,
},
$or: {
status: {
type: 'constant',
operator: '$in',
value: ['ready', 'done'],
},
height: {
type: 'constant',
operator: '$ne',
value: 30,
},
},
}Supported options are constant, varArray, varTime, $in, $ne,
$time, like, json, parseNumber, ctxField, and require.
Reads size and page from ctx.query. The offset is size * page, so page
numbering starts at zero. Response data is { list, total }.
query: query DSL configuration.extraOptions: merged into SequelizefindAndCountAlloptions.filter: synchronous mapper applied to each row.beforeQueryHook(query, extraOptions, ctx): a truthy return stops handling.afterQueryHook(results, query, extraOptions, ctx): a truthy return becomes response data.
getList uses the same query hooks and calls findAll. getSingle uses the
route id when present, otherwise it builds a query and calls findOne.
validate: Validator.js rules in{ field: { name, rule } }form.constantCreate: values assigned over request values.pickCreate: maps destination fields to source request fields.enableBatch: usesbulkCreatewhen the body is an array.extraOptions: passed to Sequelize.beforeInsertHook(createItem, ctx): a truthy return stops handling.afterInsertHook(insertItem, transaction, ctx): runs in a managed transaction and must be async.
An id route parameter is used when present. Otherwise the configured query is
built from ctx.request.body.
query,validate,extraOptions: query, validation, and Sequelize options.pick: allowlist of fields that may be updated.itemBlock: requires current model fields to match configured values.beforeUpdateHook(updateItem, item, ctx): a truthy return stops handling.afterUpdateHook(updateItem, item, raw, transaction, ctx): runs in a managed transaction and must be async.
Deletion is soft by default. physicDelete: true uses Sequelize destroy.
The handler supports query, extraOptions, beforeDeleteHook, and an async
transactional afterDeleteHook.
import { utils } from 'lowcode-controller'
utils.resSuccess(data)
utils.resFail(1, 'message')
utils.timeQuery(['2024-01-01', '2024-12-31'])
utils.validator.validateJson(data, rules)
utils.curdHelper.findByList(model, options)npm install
npm run typecheck
npm test
npm run build
npm pack --dry-runnpm publish runs the verification suite through prepublishOnly and rebuilds
dist through prepack. The existing MySQL-backed example suite is available
with npm run test:integration and uses test/instance/config.js.
ISC