create-rebe 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/template/.env.example +4 -0
  3. package/template/README.md +63 -8
  4. package/template/SECURITY.md +39 -0
  5. package/template/app/routes/api.route.js +1 -1
  6. package/template/app/routes/register.route.js +1 -1
  7. package/template/app/routes/web.route.js +1 -1
  8. package/template/app/socket/register.socket.js +0 -2
  9. package/template/config/express.config.js +8 -0
  10. package/template/core/common/string.js +1 -1
  11. package/template/core/cron.core.js +1 -0
  12. package/template/core/database.core.js +30 -17
  13. package/template/core/error.core.js +8 -4
  14. package/template/core/express.core.js +16 -4
  15. package/template/core/hooks.core.js +10 -7
  16. package/template/core/migrator.core.js +201 -0
  17. package/template/core/modules.core.js +167 -0
  18. package/template/core/queue.core.js +1 -0
  19. package/template/core/routing.core.d.ts +273 -0
  20. package/template/core/routing.core.js +666 -0
  21. package/template/core/seeder.core.js +105 -0
  22. package/template/core/socket.core.js +1 -0
  23. package/template/database/migrations/.gitkeep +0 -0
  24. package/template/database/seeders/.gitkeep +0 -0
  25. package/template/docs/Database.md +14 -8
  26. package/template/docs/Express.md +5 -2
  27. package/template/docs/Make.md +46 -0
  28. package/template/docs/Migration.md +56 -0
  29. package/template/docs/Modules.md +96 -0
  30. package/template/docs/README.md +5 -0
  31. package/template/docs/Routing.md +116 -0
  32. package/template/docs/Seeder.md +54 -0
  33. package/template/eslint.config.js +52 -0
  34. package/template/package-lock.json +1068 -70
  35. package/template/package.json +15 -8
  36. package/template/scripts/cli/args.js +39 -0
  37. package/template/scripts/cli/bootstrap.js +16 -0
  38. package/template/scripts/cli/db.js +79 -0
  39. package/template/scripts/cli/help.js +58 -0
  40. package/template/scripts/cli/keys.js +100 -0
  41. package/template/scripts/cli/log.js +58 -0
  42. package/template/scripts/cli/make.js +249 -0
  43. package/template/scripts/cli/names.js +51 -0
  44. package/template/scripts/cli/templates.js +358 -0
  45. package/template/scripts/cli.js +75 -234
  46. package/template/tests/http.test.js +99 -0
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ const logger = require('@core/logger.core')
7
+ const Database = require('@core/database.core')
8
+ const Modules = require('@core/modules.core')
9
+
10
+ const SEEDERS_DIR = path.resolve(process.cwd(), 'database', 'seeders')
11
+
12
+ // Seeder runner. Seeders are plain async functions used to populate data; unlike
13
+ // migrations they are not tracked and are meant to be re-runnable (write them to be
14
+ // idempotent — e.g. findOrCreate / upsert). Files live in database/seeders/ and/or a
15
+ // module's seeders/ dir. Files starting with "_" are ignored (base classes/helpers).
16
+ //
17
+ // module.exports = async ({ sequelize, Database, models }) => { ... }
18
+ class Seeder {
19
+ // Build the context handed to every seeder.
20
+ static async _context() {
21
+ const sequelize = await Database.connect()
22
+ return { sequelize, Database, Sequelize: Database.Sequelize, models: Object.fromEntries(Database.models) }
23
+ }
24
+
25
+ // basename -> absolute path across the flat dir and every module seeders dir.
26
+ static _discover() {
27
+ const dirs = [SEEDERS_DIR, ...Modules.dirs('seeders')]
28
+ const files = new Map()
29
+
30
+ for (const dir of dirs) {
31
+ if (!fs.existsSync(dir)) continue
32
+ for (const file of fs.readdirSync(dir)) {
33
+ if (!file.endsWith('.js') || file.startsWith('_')) continue
34
+ if (!files.has(file)) files.set(file, path.join(dir, file))
35
+ }
36
+ }
37
+
38
+ return new Map([...files.entries()].sort(([a], [b]) => a.localeCompare(b)))
39
+ }
40
+
41
+ static _run(file, ctx) {
42
+ const raw = require(file)
43
+ const fn = raw && raw.default !== undefined ? raw.default : raw
44
+ if (typeof fn !== 'function') {
45
+ throw new Error(`Seeder "${path.basename(file)}" must export an async function`)
46
+ }
47
+ return fn(ctx)
48
+ }
49
+
50
+ // Run every discovered seeder, in filename order.
51
+ static async seedAll() {
52
+ const ctx = await Seeder._context()
53
+ const all = Seeder._discover()
54
+
55
+ if (!all.size) {
56
+ logger.info('Seeders: nothing to seed')
57
+ return []
58
+ }
59
+
60
+ const ran = []
61
+ for (const [name, file] of all) {
62
+ logger.info(`Seeding: ${name}`)
63
+ await Seeder._run(file, ctx)
64
+ ran.push(name)
65
+ }
66
+
67
+ logger.info(`Seeders: ${ran.length} run`)
68
+ return ran
69
+ }
70
+
71
+ // Run a single seeder. With no name, run database/seeders/index.js (the default
72
+ // "DatabaseSeeder" entry) when present, otherwise fall back to seeding all.
73
+ static async seed(name) {
74
+ const ctx = await Seeder._context()
75
+
76
+ if (!name) {
77
+ const indexFile = path.join(SEEDERS_DIR, 'index.js')
78
+ if (fs.existsSync(indexFile)) {
79
+ logger.info('Seeding: index.js')
80
+ await Seeder._run(indexFile, ctx)
81
+ return ['index.js']
82
+ }
83
+ return Seeder.seedAll()
84
+ }
85
+
86
+ const all = Seeder._discover()
87
+ // Match leniently: exact filename, or normalized (separators/case stripped) so
88
+ // `--class=UserSeeder` finds `user-seeder.js`.
89
+ const norm = (s) =>
90
+ s
91
+ .replace(/\.js$/, '')
92
+ .replace(/[^a-z0-9]/gi, '')
93
+ .toLowerCase()
94
+ const wanted = norm(name)
95
+ const target = all.get(name) || all.get(`${name}.js`) || [...all.entries()].find(([k]) => norm(k) === wanted)?.[1]
96
+
97
+ if (!target) throw new Error(`Seeder "${name}" not found in ${[SEEDERS_DIR, ...Modules.dirs('seeders')].join(', ')}`)
98
+
99
+ logger.info(`Seeding: ${path.basename(target)}`)
100
+ await Seeder._run(target, ctx)
101
+ return [path.basename(target)]
102
+ }
103
+ }
104
+
105
+ module.exports = Seeder
@@ -68,6 +68,7 @@ class Socket {
68
68
 
69
69
  static async _loadHandlers() {
70
70
  await Register.invoke(REGISTER_FILE, [Socket.io, Socket], { label: 'app/socket/register.socket.js' })
71
+ await require('@core/modules.core').invoke('socket', [Socket.io, Socket])
71
72
  }
72
73
 
73
74
  static broadcast(event, payload) {
File without changes
File without changes
@@ -1,8 +1,9 @@
1
1
  # Database API
2
2
 
3
3
  `@core/database.core` manages the Sequelize connection and model loading. It is
4
- skipped entirely when `DB_ENABLED=false`. Model/migration generators are
5
- intentionally out of scope for this framework.
4
+ skipped entirely when `DB_ENABLED=false`. Schema is managed by the migration runner
5
+ ([Migration.md](./Migration.md)) and populated by seeders ([Seeder.md](./Seeder.md));
6
+ generate models/migrations/seeders with the CLI (see the [project README](../README.md#cli)).
6
7
 
7
8
  ```js
8
9
  const Database = require('@core/database.core')
@@ -10,12 +11,17 @@ const Database = require('@core/database.core')
10
11
 
11
12
  ## Methods
12
13
 
13
- | Method | Returns | Notes |
14
- | -------------- | --------- | ----------------------------------------------------- |
15
- | `connect()` | Sequelize | idempotent; `authenticate()` then `loadModels()` |
16
- | `disconnect()` | Promise | closes the pool, clears models |
17
- | `loadModels()` | `Map` | loads `database/models/*.js`, then wires associations |
18
- | `model(name)` | Model | throws if the model is not registered |
14
+ | Method | Returns | Notes |
15
+ | -------------- | ---------- | --------------------------------------------------- |
16
+ | `connect()` | Sequelize | idempotent; `authenticate()` then `loadModels()` |
17
+ | `disconnect()` | Promise | closes the pool, clears models |
18
+ | `loadModels()` | `Map` | loads flat + module models, then wires associations |
19
+ | `modelDirs()` | `string[]` | `database/models` plus each module's `models/` dir |
20
+ | `model(name)` | Model | throws if the model is not registered |
21
+
22
+ Models are loaded from `database/models/*.js` **and** every module's `models/` dir
23
+ (see [Modules.md](./Modules.md)); a name collision logs a warning and the later
24
+ definition wins.
19
25
 
20
26
  Static re-exports: `Database.Sequelize`, `Database.DataTypes`, `Database.Model`,
21
27
  `Database.Op`.
@@ -26,11 +26,14 @@ These are wired by `bootstrap.core`; `upload()` is the one you call from routes.
26
26
  3. `compression()`
27
27
  4. `express.json` / `express.urlencoded` (limit = `EXPRESS_BODY_LIMIT`)
28
28
  5. rate limit (when `EXPRESS_RATE_LIMIT_ENABLED`)
29
- 6. user middleware — `app/http/middlewares/register.middleware.js` `(app) => {}`
29
+ 6. user middleware — `app/http/middlewares/register.middleware.js` `(app) => {}`, then module `middlewares` hooks
30
30
  7. static — `./public` at `/`, uploads at `/uploads`
31
- 8. routes — `@routes/register.route` then `Routes.apply`
31
+ 8. routes — `@routes/register.route` + module routes, then `Routes.apply` (see [Routing.md](./Routing.md))
32
32
  9. `error.core` 404 + error handlers
33
33
 
34
+ The router (`@core/routing.core`) is internalized into the core as of 3.0.0 — there is
35
+ no external routing dependency.
36
+
34
37
  `x-powered-by` is disabled. The full `storage/` root is **not** served — only
35
38
  `/uploads` and `./public`.
36
39
 
@@ -0,0 +1,46 @@
1
+ # Scaffolding (`make:`) commands
2
+
3
+ The CLI generates code for every app layer. Run via `npm run cli -- <command>`. Names
4
+ accept any case (`UserProfile`, `user_profile`, `user-profile`) and are normalized:
5
+ PascalCase for classes, kebab-case for filenames, snake_case (pluralized) for tables.
6
+
7
+ **Every `make:` command supports `--module=<name>`** to target `modules/<name>/…`
8
+ instead of the flat app, and `--force` to overwrite.
9
+
10
+ | Command | Creates | Notes |
11
+ | ------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------------- |
12
+ | `make:model <Name> [--table=t] [--no-migration]` | `database/models/<name>.model.js` + create-table migration | `--no-migration` skips the migration |
13
+ | `make:migration <name>` | `database/migrations/<ts>_<name>.js` | blank up/down |
14
+ | `make:seed <Name>` | `database/seeders/<name>.js` | re-runnable seeder |
15
+ | `make:controller <Name> [--resource] [--api]` | `app/http/controllers/<name>.controller.js` | `--resource` = 7 RESTful actions; `--api` = without create/edit |
16
+ | `make:middleware <Name>` | `app/http/middlewares/<name>.middleware.js` | `handle({ req, res, next })` class |
17
+ | `make:validator <Name>` | `app/http/validators/<name>.validator.js` | `create…Schema` / `update…Schema` (zod) |
18
+ | `make:route <name>` | `app/routes/<name>.route.js` | `Routes.group(...)` file |
19
+ | `make:job <Name>` | `app/jobs/<name>.job.js` | `(Cron) => Cron.define(...)` |
20
+ | `make:queue <Name>` | `app/queue/<name>.queue.js` | `(Queue) => Queue.define(...)` |
21
+ | `make:socket <Name>` | `app/socket/<name>.socket.js` | `(io, Socket) => {}` |
22
+ | `make:hook [name]` | `app/hooks/<name>.hook.js` (or `register.hook.js`) | `{ before, after, shutdown }` |
23
+ | `make:resource <Name> [--api]` | controller + model + migration + validator + route | a wired vertical slice |
24
+ | `make:module <name>` | a full mini-app under `modules/<name>/` | see [Modules.md](./Modules.md) |
25
+
26
+ ## Wiring notes
27
+
28
+ Some layers load through a single register file, so the CLI prints a one-line hint:
29
+
30
+ - **Routes** — flat `app/routes/<name>.route.js` must be required from
31
+ `app/routes/register.route.js`. Inside a **module**, route files in `routes/` are
32
+ **auto-loaded** (drop one in and it works).
33
+ - **Jobs / queue / socket** — flat files are invoked from the matching
34
+ `app/.../register.*.js`; module files are wired by the module entry.
35
+
36
+ ## Examples
37
+
38
+ ```bash
39
+ npm run cli -- make:resource Post # full CRUD slice (web)
40
+ npm run cli -- make:resource Post --api # JSON-only CRUD
41
+ npm run cli -- make:controller Billing --resource
42
+ npm run cli -- make:middleware EnsureAdmin
43
+ npm run cli -- make:module blog # full mini-app module
44
+ npm run cli -- make:resource Article --module=blog --api
45
+ npm run cli -- db:migrate # apply the generated migrations
46
+ ```
@@ -0,0 +1,56 @@
1
+ # Migration API
2
+
3
+ `@core/migrator.core` is a lightweight schema-migration runner built on Sequelize's
4
+ `QueryInterface` — no extra dependencies. It replaces relying on `sync({ force, alter })`
5
+ for managing schema. Applied migrations are tracked in the `_migrations` table.
6
+
7
+ ```js
8
+ const Migrator = require('@core/migrator.core')
9
+ ```
10
+
11
+ Migrations live in `database/migrations/*.js` and/or any module's `migrations/` dir.
12
+ Files are ordered **globally by filename**, so the `YYYYMMDDHHmmss_` timestamp prefix
13
+ the CLI generates gives deterministic order across the flat dir and every module.
14
+ Files whose name starts with `_` are ignored (helpers/partials).
15
+
16
+ ## Writing a migration
17
+
18
+ ```js
19
+ // database/migrations/20260619101540_create_articles.js
20
+ module.exports = {
21
+ async up(queryInterface, Sequelize) {
22
+ await queryInterface.createTable('articles', {
23
+ id: { type: Sequelize.BIGINT.UNSIGNED, primaryKey: true, autoIncrement: true },
24
+ title: { type: Sequelize.STRING(190), allowNull: false, unique: true },
25
+ created_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') },
26
+ updated_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') },
27
+ })
28
+ },
29
+ async down(queryInterface) {
30
+ await queryInterface.dropTable('articles')
31
+ },
32
+ }
33
+ ```
34
+
35
+ Generate one with `make:model <Name>` (model + create-table migration) or
36
+ `make:migration <name>` (blank up/down) — see the CLI table in the
37
+ [project README](../README.md#cli).
38
+
39
+ ## Methods
40
+
41
+ | Method | Returns | Notes |
42
+ | -------------------- | ---------- | ------------------------------------------------------------ |
43
+ | `up()` | `string[]` | run every pending migration under the next batch number |
44
+ | `down({ step = 1 })` | `string[]` | roll back the most recent batch (or the last `step` batches) |
45
+ | `rollbackAll()` | `Promise` | roll back every batch, one at a time |
46
+ | `status()` | `object[]` | `[{ name, applied, batch }]` for all discovered migrations |
47
+ | `dropAllTables()` | `string[]` | drop every table (FK checks toggled per dialect) |
48
+
49
+ ## Batches
50
+
51
+ Each `up()` run records its migrations under a single incrementing `batch`. `down()`
52
+ rolls back the latest batch as a unit, so a group migrated together rolls back together.
53
+ `--step=N` on `db:rollback` extends this to the last `N` batches.
54
+
55
+ > Once migrations are the source of truth, keep `DB_FORCE` / `DB_ALTER` **off** in
56
+ > production — let migrations own the schema.
@@ -0,0 +1,96 @@
1
+ # Modules API
2
+
3
+ `@core/modules.core` is an **opt-in** layer that groups one feature's pieces — models,
4
+ routes, migrations, seeders, jobs, queue workers, socket handlers — under
5
+ `modules/<name>/` behind a single entry file. It is fully additive: the flat layout
6
+ (`database/models`, `app/routes`, …) keeps working untouched, and a project with no
7
+ `modules/` directory pays nothing.
8
+
9
+ ```js
10
+ const Modules = require('@core/modules.core')
11
+ ```
12
+
13
+ ## Anatomy of a module
14
+
15
+ `make:module <name>` scaffolds a **full mini-app** mirroring the app layout:
16
+
17
+ ```
18
+ modules/
19
+ └── blog/
20
+ ├── blog.module.js # entry (also accepts module.js / index.js)
21
+ ├── http/
22
+ │ ├── controllers/ # static-class controllers
23
+ │ ├── middlewares/register.middleware.js # (app) => {}
24
+ │ └── validators/ # zod schemas
25
+ ├── routes/ # *.js auto-loaded (drop in <name>.route.js)
26
+ ├── jobs/register.job.js # (Cron) => {}
27
+ ├── queue/register.queue.js # (Queue) => {}
28
+ ├── socket/register.socket.js # (io, Socket) => {}
29
+ ├── hooks/register.hook.js # { before, after, shutdown }
30
+ ├── models/ # (sequelize, DataTypes) factories
31
+ ├── migrations/ # up/down migration files
32
+ └── seeders/ # seeder files
33
+ ```
34
+
35
+ Target a module with `--module=<name>` on any `make:` command (e.g.
36
+ `make:resource Article --module=blog`); files land in the right subfolder. Route files
37
+ in `routes/` are auto-loaded, so a generated `<name>.route.js` works with no wiring.
38
+
39
+ ## Entry file
40
+
41
+ Every field is optional. Directory fields resolve against the module folder; the
42
+ function/object fields mirror the flat `app/.../register.*.js` shapes.
43
+
44
+ ```js
45
+ // modules/blog/blog.module.js
46
+ module.exports = {
47
+ name: 'blog', // optional, defaults to the folder name
48
+
49
+ // scanned directories
50
+ models: './models', // dir scanned by database.core
51
+ migrations: './migrations', // dir scanned by migrator.core
52
+ seeders: './seeders', // dir scanned by seeder.core
53
+ routes: './routes', // file → required; dir → every *.js auto-loaded
54
+
55
+ // register hooks
56
+ middlewares: require('./http/middlewares/register.middleware'), // (app) => {}
57
+ jobs: require('./jobs/register.job'), // (Cron) => {}
58
+ queue: require('./queue/register.queue'), // (Queue) => {}
59
+ socket: require('./socket/register.socket'), // (io, Socket) => {}
60
+ hooks: require('./hooks/register.hook'), // { before, after, shutdown }
61
+ }
62
+ ```
63
+
64
+ The function hooks and `hooks` lifecycle object receive the exact same facades/context as
65
+ the flat `app/.../register.*.js` files and run **after** them during boot.
66
+
67
+ ## How discovery wires in
68
+
69
+ | Contribution | Loaded by | When |
70
+ | ------------- | -------------------------------------- | --------------------- |
71
+ | `models` | `database.core.loadModels()` | DB connect |
72
+ | `migrations` | `migrator.core` | CLI `db:*` |
73
+ | `seeders` | `seeder.core` | CLI `db:seed*` |
74
+ | `routes` | `express.core` (after flat routes) | HTTP boot |
75
+ | `middlewares` | `express.core` (after flat middleware) | HTTP boot |
76
+ | `jobs` | `cron.core` (after flat register) | Cron start |
77
+ | `queue` | `queue.core` (after flat register) | Queue start |
78
+ | `socket` | `socket.core` (after flat register) | Socket attach |
79
+ | `hooks` | `hooks.core` (after the flat app hook) | before/after/shutdown |
80
+
81
+ Folders (and modules) whose name starts with `_` are skipped. A module with no entry
82
+ file is skipped with a warning. The core layer never imports module code statically —
83
+ modules are app-land, discovered here at runtime, so the dependency arrow stays
84
+ one-directional (`app → core`).
85
+
86
+ ## Methods
87
+
88
+ | Method | Returns | Notes |
89
+ | ---------------------- | ---------- | ------------------------------------------------------------------------ |
90
+ | `discover()` | `object[]` | cached `[{ name, dir, def }]` for every module |
91
+ | `dirs(kind)` | `string[]` | absolute, existing dirs for `'models' \| 'migrations' \| 'seeders'` |
92
+ | `requireRoutes()` | `void` | load each module's routes (dir → every `*.js`, file → that file) |
93
+ | `invoke(kind, args)` | `Promise` | call each module's `'middlewares' \| 'jobs' \| 'queue' \| 'socket'` hook |
94
+ | `runHooks(stage, ctx)` | `Promise` | run each module's `hooks[stage]` (`before` \| `after` \| `shutdown`) |
95
+ | `list()` | `string[]` | module names |
96
+ | `reset()` | `void` | clear the discovery cache (tooling/tests) |
@@ -14,11 +14,16 @@ Per-core API documentation. For project setup, structure, and the boot flow, see
14
14
  | Redis (optional) | [Redis.md](./Redis.md) |
15
15
  | Hooks | [Hooks.md](./Hooks.md) |
16
16
  | Express (HTTP) | [Express.md](./Express.md) |
17
+ | Routing | [Routing.md](./Routing.md) |
17
18
  | Socket.IO | [Socket.md](./Socket.md) |
18
19
  | Validator | [Validator.md](./Validator.md) |
19
20
  | Cron | [Cron.md](./Cron.md) |
20
21
  | Queue | [Queue.md](./Queue.md) |
21
22
  | Database | [Database.md](./Database.md) |
23
+ | Migration | [Migration.md](./Migration.md) |
24
+ | Seeder | [Seeder.md](./Seeder.md) |
25
+ | Modules (optional) | [Modules.md](./Modules.md) |
26
+ | Scaffolding (`make:`) | [Make.md](./Make.md) |
22
27
  | Common (utils) | [Common.md](./Common.md) |
23
28
  | Register (app→core bridge) | [Register.md](./Register.md) |
24
29
  | Bootstrap | [Bootstrap.md](./Bootstrap.md) |
@@ -0,0 +1,116 @@
1
+ # Routing API
2
+
3
+ `@core/routing.core` is the Laravel-style router (internalized in 3.0.0 — previously the
4
+ external `@refkinscallv/express-routing` package). Define routes against the static
5
+ `Routes` class; `express.core` calls `Routes.apply(app, router)` during boot.
6
+
7
+ ```js
8
+ const Routes = require('@core/routing.core')
9
+ ```
10
+
11
+ Every handler and `handle()`-based middleware receives the context
12
+ `{ req, res, next, error }` (`error` is only populated in the error handler).
13
+
14
+ ## Defining routes
15
+
16
+ ```js
17
+ Routes.group('api', () => {
18
+ Routes.get('status', ({ res }) => res.json({ status: true, code: 200, message: 'OK', data: null, meta: null }))
19
+ Routes.post('users', UserController.store, [Validator.make(createUserSchema)])
20
+ Routes.get('users/:id', UserController.show).whereNumber('id').name('users.show')
21
+ })
22
+ ```
23
+
24
+ | Method | Notes |
25
+ | ------------------------------------------------------------- | -------------------------------------------------- |
26
+ | `get/post/put/delete/patch/options/head(path, handler, mws?)` | register one route; returns a chainable handle |
27
+ | `add(methods, path, handler, mws?)` | one or many methods at once |
28
+ | `group(prefix, callback, mws?)` | nest routes under a URL prefix + shared middleware |
29
+ | `redirect(from, to, status=302)` | redirect route |
30
+ | `view(path, view, data?)` | render via the Express view engine |
31
+
32
+ Handlers may be an inline `({ req, res }) => …`, a `[Controller, 'method']` tuple, or a
33
+ controller method reference (`UserController.show`).
34
+
35
+ ## Chainable registration
36
+
37
+ ```js
38
+ Routes.get('users/:id', handler)
39
+ .name('users.show') // for Routes.url()/route()
40
+ .whereNumber('id') // constrain :id to [0-9]+
41
+ .where('slug', '[a-z-]+') // custom regex (string or RegExp)
42
+ ```
43
+
44
+ `whereNumber`, `whereAlpha`, `whereAlphaNumeric`, `whereUuid` are shorthands. A request
45
+ whose param fails the constraint falls through (`next('route')`) so a later route can match.
46
+
47
+ ## Middleware
48
+
49
+ Middleware can be a plain Express function, a class/object with
50
+ `handle({ req, res, next, error })`, or a registered **alias/group** name (string).
51
+
52
+ ```js
53
+ // Named aliases & groups (Laravel-style)
54
+ Routes.registerMiddleware('auth', AuthMiddleware)
55
+ Routes.registerMiddleware({ admin: AdminMiddleware, guest: GuestMiddleware })
56
+ Routes.middlewareGroup('web', [SessionMw, CsrfMw])
57
+
58
+ // Scoped — accepts plain functions OR handle() classes:
59
+ Routes.middleware([AuthMiddleware, logFn], () => {
60
+ Routes.get('me', UserController.me)
61
+ })
62
+
63
+ // Chaining — STRICT: only handle() classes/objects/aliases (no plain functions):
64
+ Routes.middleware(['auth']).get('me', UserController.me)
65
+ Routes.middleware([AuthMiddleware]).group('admin', () => {
66
+ /* ... */
67
+ })
68
+ ```
69
+
70
+ Per-route middleware is the optional 3rd argument: `Routes.get(path, handler, [Mw])`.
71
+
72
+ ## Controllers & resources
73
+
74
+ ```js
75
+ // Auto-register every public method of a controller (methods starting with "_" are private):
76
+ // index -> /base · samplePath -> /base/sample-path · post_create -> POST /base/create
77
+ Routes.controller('users', UserController, { index: [AuthMiddleware] })
78
+
79
+ // The seven RESTful routes (only actions the controller implements are mounted):
80
+ Routes.resource('photos', PhotoController) // index/create/store/show/edit/update/destroy
81
+ Routes.apiResource('photos', PhotoController) // same, minus create/edit (HTML forms)
82
+ Routes.resource('photos', PhotoController, { only: ['index', 'show'], parameter: 'photo', middleware: [AuthMiddleware] })
83
+ ```
84
+
85
+ Generate these with `make:controller --resource` / `make:resource` (see [Make.md](./Make.md)).
86
+
87
+ ## Named routes & URL generation
88
+
89
+ ```js
90
+ Routes.get('users/:id', handler).name('users.show')
91
+ Routes.url('users.show', { id: 5 }) // → /users/5
92
+ Routes.route('users.show', { id: 5, tab: 'a' }) // → /users/5?tab=a (alias of url())
93
+ ```
94
+
95
+ Missing required params throw; extra keys become query string; values are
96
+ `encodeURIComponent`-escaped.
97
+
98
+ ## App-level handlers
99
+
100
+ | Method | Purpose |
101
+ | -------------------------------- | ------------------------------------------------------------------------ |
102
+ | `errorHandler(handler)` | global error handler — receives `{ req, res, next, error }` |
103
+ | `fallback(handler)` | runs when no route matched (before the framework 404) |
104
+ | `maintenance(enabled, handler?)` | when on, every request gets 503 before routes |
105
+ | `allRoutes()` | `[{ methods, path, name, middlewareCount, handlerType }]` for inspection |
106
+
107
+ ## Applying routes
108
+
109
+ `express.core` already calls this — you rarely call it directly:
110
+
111
+ ```js
112
+ await Routes.apply(app) // mount on app
113
+ await Routes.apply(app, router) // mount on a router, then app.use(router)
114
+ ```
115
+
116
+ Types ship at `core/routing.core.d.ts` for editor IntelliSense.
@@ -0,0 +1,54 @@
1
+ # Seeder API
2
+
3
+ `@core/seeder.core` runs data seeders. Unlike migrations, seeders are **not tracked**
4
+ and are meant to be re-runnable — write them idempotently (`findOrCreate` / `upsert`).
5
+
6
+ ```js
7
+ const Seeder = require('@core/seeder.core')
8
+ ```
9
+
10
+ Seeders live in `database/seeders/*.js` and/or any module's `seeders/` dir. Files run
11
+ in filename order (prefix with numbers to control sequence). Files starting with `_`
12
+ are ignored (base classes/helpers).
13
+
14
+ ## Writing a seeder
15
+
16
+ Each seeder exports an async function receiving a context object:
17
+
18
+ ```js
19
+ // database/seeders/article-seeder.js
20
+ module.exports = async ({ Database, sequelize, Sequelize, models }) => {
21
+ const Article = Database.model('Article')
22
+ for (const title of ['Hello World', 'Second Post']) {
23
+ await Article.findOrCreate({ where: { title }, defaults: { title } })
24
+ }
25
+ }
26
+ ```
27
+
28
+ | Context key | What it is |
29
+ | ----------- | ------------------------------------------------- |
30
+ | `Database` | the database core (`Database.model('Name')`) |
31
+ | `sequelize` | the live Sequelize instance |
32
+ | `Sequelize` | the Sequelize constructor (DataTypes, `Op`, …) |
33
+ | `models` | a plain object of all loaded models keyed by name |
34
+
35
+ Generate one with `make:seed <Name>`.
36
+
37
+ ## Methods
38
+
39
+ | Method | Returns | Notes |
40
+ | ------------ | ---------- | ---------------------------------------------------------------------------- |
41
+ | `seedAll()` | `string[]` | run every discovered seeder, in filename order |
42
+ | `seed(name)` | `string[]` | run one seeder; matches filename leniently (`UserSeeder` ↔ `user-seeder.js`) |
43
+
44
+ With no `name`, `seed()` runs `database/seeders/index.js` (a "DatabaseSeeder" entry that
45
+ can call others) when present, otherwise falls back to running all seeders.
46
+
47
+ ## CLI
48
+
49
+ ```bash
50
+ npm run cli -- db:seed # default seeder (index.js) or all
51
+ npm run cli -- db:seed --class=ArticleSeeder
52
+ npm run cli -- db:seed --all # every seeder (alias: seed-all)
53
+ npm run cli -- db:reset --seed # drop → migrate → seed
54
+ ```
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const js = require('@eslint/js')
4
+ const globals = require('globals')
5
+ const security = require('eslint-plugin-security')
6
+ const n = require('eslint-plugin-n').default
7
+
8
+ // Flat config (ESLint 10). Correctness via @eslint/js recommended + node best practices
9
+ // (eslint-plugin-n), security smells via eslint-plugin-security. Prettier owns
10
+ // formatting, so no stylistic rules here.
11
+ module.exports = [
12
+ {
13
+ ignores: ['node_modules/**', 'coverage/**', 'logs/**', 'storage/**', 'public/**', 'create-rebe/template/**', '**/*.d.ts'],
14
+ },
15
+
16
+ js.configs.recommended,
17
+ n.configs['flat/recommended-script'],
18
+ security.configs.recommended,
19
+
20
+ {
21
+ languageOptions: {
22
+ ecmaVersion: 2023,
23
+ sourceType: 'commonjs',
24
+ globals: { ...globals.node },
25
+ },
26
+ rules: {
27
+ // Register/handler callbacks routinely receive facade args they may not use
28
+ // (e.g. (io, Socket) => {}); don't flag unused args, only unused vars/imports.
29
+ 'no-unused-vars': ['warn', { args: 'none', varsIgnorePattern: '^_' }],
30
+ 'no-extend-native': 'error',
31
+
32
+ // The framework is a loader/scaffolder by design: it requires modules and
33
+ // reads files from computed paths, and shells out from the setup CLI. These
34
+ // security rules fire on that intentional behavior, so they are disabled to
35
+ // keep the genuine findings (eval, unsafe regex, timing, weak crypto) signal-rich.
36
+ 'security/detect-non-literal-require': 'off',
37
+ 'security/detect-non-literal-fs-filename': 'off',
38
+ 'security/detect-child-process': 'off',
39
+ 'security/detect-object-injection': 'off',
40
+
41
+ // module-alias (@core/*, @config/*) is resolved at runtime, not by eslint-plugin-n.
42
+ 'n/no-missing-require': 'off',
43
+ 'n/no-unpublished-require': 'off',
44
+ 'n/no-process-exit': 'off',
45
+ },
46
+ },
47
+
48
+ {
49
+ files: ['tests/**/*.js'],
50
+ languageOptions: { globals: { ...globals.jest } },
51
+ },
52
+ ]