Generators
Master’s generators are idempotent and wiring-aware: they create files and also register routes (in routes.js) and models (in AppContext.js) for you. Run them from anywhere inside a Master project (a directory with master.config.js).
$ master generate <type> <name> [extra...]$ master g <type> <name> [extra...] # alias
scaffold — a full vertical slice#
The headline generator. One command produces a model, a validated REST controller, typed routes, and a Next.js page.
$ master g scaffold post title:string body:text
Creates and wires:
backend/app/models/Post.js— entity, registered inAppContext.jsbackend/app/controllers/postsController.js— a RESTful JSON controller backed by the request-scoped context (this.db), withstatic schemasderived from your fields (model binding + validation →this.model, automatic400 ValidationProblemDetails) and typed results (this.ok / created / noContent / notFound). A commentedstatic authorizeshows where to require a signed-in user for writes.frontend/app/posts/page.tsx— a Server Component list page that fetches/posts- 5 routes appended to
routes.jswith a typed:id(int)parameter —/posts/abcis a 404, not a query:
| Method | Path | Action | Replies |
|---|---|---|---|
| GET | /posts | index | 200 { data: [...] } |
| GET | /posts/:id(int) | show | 200 / 404 |
| POST | /posts | create | 201 + Location / 400 |
| PUT | /posts/:id(int) | update | 200 / 400 / 404 |
| DELETE | /posts/:id(int) | destroy | 204 / 404 |
export default class Post {
id(db) { db.integer().primary().auto(); }
title(db) { db.string(); }
body(db) { db.text(); }
}// PostsController — RESTful JSON API backed by MasterRecord.
// this.db is a request-scoped AppContext (ASP.NET/EF: scoped DbContext) — registered in server.js.
// Input is bound + validated from `static schemas` into this.model (invalid -> 400 ValidationProblemDetails).
export default class PostsController {
static schemas = {
create: { body: { title: { type: 'string', required: true }, body: { type: 'string', required: true } } },
update: { body: { title: { type: 'string' }, body: { type: 'string' } } },
};
// Protect writes once auth is configured in server.js (ASP.NET [Authorize]):
// static authorize = { create: true, update: true, destroy: true };
constructor(requestObject) {
this.requestObject = requestObject;
}
// GET /posts
async index() {
const data = await this.db.Post.toList();
this.ok({ data });
}
// GET /posts/:id
async show(obj) {
const item = await this.db.Post.find(obj.params.id);
if (!item) return this.notFound('Post not found');
this.ok({ data: item });
}
// POST /posts
async create() {
const item = this.db.Post.new();
Object.assign(item, this.model);
await this.db.saveChanges();
this.created(`/posts/${item.id}`, { data: item });
}
// PUT /posts/:id
async update(obj) {
const item = await this.db.Post.find(obj.params.id);
if (!item) return this.notFound('Post not found');
Object.assign(item, this.model);
await this.db.saveChanges();
this.ok({ data: item });
}
// DELETE /posts/:id
async destroy(obj) {
const item = await this.db.Post.find(obj.params.id);
if (!item) return this.notFound('Post not found');
this.db.Post.remove(item);
await this.db.saveChanges();
this.noContent();
}
}The validation rule for each field follows its column type (string/text → string, integer → integer, boolean → boolean, datetime → datetime, uuid → uuid, …); every field is required on create (booleans excepted) and optional on update.
Then: master db new AddPosts && master db migrate. The new endpoints appear in /openapi.json and the Swagger UI at /docs automatically.
controller#
$ master g controller users index show profile
Creates backend/app/controllers/usersController.js with one action per name (default index) and a GET route for each (/users, /users/show, /users/profile). Actions reply with this.ok(...); the header comments show where static authorize and static schemas go.
// UsersController — MasterController v2 API controller (ESM).
// Each request constructs a fresh instance; actions receive the request object and
// reply with typed results: this.ok / created / noContent / notFound / problem / file.
// this.db = request-scoped AppContext, this.user = ClaimsPrincipal, this.logger, this.options.
export default class UsersController {
// Authorization (ASP.NET [Authorize]) once auth is configured in server.js:
// static authorize = { '*': true, index: false };
// Model binding + validation (ASP.NET [ApiController]):
// static schemas = { create: { body: { name: 'string!' } } };
constructor(requestObject) {
this.requestObject = requestObject;
}
// GET-by-default action. Reply with JSON for the Next.js frontend to consume.
async index(obj) {
this.ok({
ok: true,
controller: 'users',
action: 'index',
params: obj.params,
user: this.user && this.user.isAuthenticated ? this.user.name : null,
});
}
}model#
$ master g model Comment body:text author:string created_at:datetime
Creates backend/app/models/Comment.js with an auto-increment id and registers its dbset in AppContext.js. Field types map to native columns — see the field-type reference. Add relationships and modifiers by hand:
export default class Comment {
id(db) { db.integer().primary().auto(); }
body(db) { db.text(); }
author(db) { db.string(); }
created_at(db) { db.datetime(); }
post_id(db) { db.belongsTo('Post'); }
}page#
$ master g page about
Creates frontend/app/about/page.tsx, a Next.js App Router Server Component that imports the api() helper.
socket / middleware / component#
$ master g socket chat message typing # app/sockets/chatSocket.js — io({ query: { socket: 'chat' } })$ master g middleware auth # middleware/NN-auth.js — (ctx, next), auto-loaded alphabetically$ master g component billing # components/billing/{config/routes.js, app/controllers/...}
A component is a mountable mini-app; register it in server.js after setupServer with await master.component('components', 'billing').
Field syntax#
string/str, text, mediumtext, longtext, int/integer/number, bigint, float, decimal, bool/boolean, time, date, datetime, timestamp, json, uuid, binary/blob. Unknown types fall back to string.