db-model-router 1.0.8 → 1.0.10

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.
package/demo/package.json CHANGED
@@ -37,6 +37,10 @@
37
37
  "swagger-ui-express": "latest"
38
38
  },
39
39
  "devDependencies": {
40
- "nodemon": "latest"
40
+ "nodemon": "latest",
41
+ "mocha": "latest",
42
+ "supertest": "latest",
43
+ "dotenv-cli": "latest",
44
+ "@faker-js/faker": "latest"
41
45
  }
42
46
  }
@@ -17,13 +17,6 @@ import couponsRoute from "#routes/coupons/index.js";
17
17
  import ordersRoute from "#routes/orders/index.js";
18
18
  import productsRoute from "#routes/products/index.js";
19
19
  import wishlistsRoute from "#routes/wishlists/index.js";
20
- import product_imagesChildRoute from "#routes/products/product_images/index.js";
21
- import product_variantsChildRoute from "#routes/products/product_variants/index.js";
22
- import product_reviewsChildRoute from "#routes/products/product_reviews/index.js";
23
- import cart_itemsChildRoute from "#routes/carts/cart_items/index.js";
24
- import order_itemsChildRoute from "#routes/orders/order_items/index.js";
25
- import paymentsChildRoute from "#routes/orders/payments/index.js";
26
- import shipmentsChildRoute from "#routes/orders/shipments/index.js";
27
20
  import docsRoute from "#routes/docs.js";
28
21
 
29
22
  // SaaS routes
@@ -34,13 +27,6 @@ router.use("/roles", saasRolesRoute);
34
27
  router.use("/roles/:role_id/permissions", saasPermissionsRoute);
35
28
 
36
29
  router.use("/docs", docsRoute);
37
- router.use("/products/:product_id/product_images", product_imagesChildRoute);
38
- router.use("/products/:product_id/product_variants", product_variantsChildRoute);
39
- router.use("/products/:product_id/product_reviews", product_reviewsChildRoute);
40
- router.use("/carts/:cart_id/cart_items", cart_itemsChildRoute);
41
- router.use("/orders/:order_id/order_items", order_itemsChildRoute);
42
- router.use("/orders/:order_id/payments", paymentsChildRoute);
43
- router.use("/orders/:order_id/shipments", shipmentsChildRoute);
44
30
 
45
31
  // Schema-generated routes
46
32
  router.use("/addresses", addressesRoute);
@@ -13,7 +13,7 @@ const SUPER_ADMIN_EMAIL = "admin@system.local";
13
13
  * Generated password for the Super Admin.
14
14
  * This is cryptographically random and unique per generation.
15
15
  */
16
- const SUPER_ADMIN_PASSWORD = "ba7e10b824a6c38c0cfb05bad715bc36";
16
+ const SUPER_ADMIN_PASSWORD = "d8a53d980af3a1516a211b4875dcec77";
17
17
 
18
18
  /**
19
19
  * Super Admin permissions: all actions for all modules with global scope.
@@ -0,0 +1,393 @@
1
+ # dbmr.schema.json — Column Rule Specification
2
+
3
+ This document defines the full column rule syntax for `dbmr.schema.json`. Column rules are pipe-delimited strings that describe the **data type**, **sub-type**, and **validation constraints** for each column.
4
+
5
+ ---
6
+
7
+ ## Syntax
8
+
9
+ ```
10
+ [required|]<type>[:<subtype>][|<validator>[|<validator>...]]
11
+ ```
12
+
13
+ **Parts:**
14
+
15
+ | Part | Required | Description |
16
+ | ----------- | -------- | -------------------------------------------------------------- |
17
+ | `required` | No | Prefix — marks column as NOT NULL |
18
+ | `type` | Yes | Base data type (see table below) |
19
+ | `:subtype` | No | Colon-suffixed sub-type for finer SQL type control |
20
+ | `validator` | No | One or more validation rules (node-input-validator compatible) |
21
+
22
+ **Examples:**
23
+
24
+ ```
25
+ "name": "required|string" # VARCHAR(255) NOT NULL
26
+ "description": "string:text" # TEXT, nullable
27
+ "body": "required|string:longtext" # LONGTEXT NOT NULL
28
+ "email": "required|string|email|maxLength:255" # VARCHAR(255) NOT NULL + email validation
29
+ "phone": "string|phoneNumber" # VARCHAR(255) + phone validation
30
+ "stock": "required|integer:unsigned" # INT UNSIGNED NOT NULL
31
+ "view_count": "integer:bigint" # BIGINT, nullable
32
+ "price": "required|numeric:decimal(10,4)" # DECIMAL(10,4) NOT NULL
33
+ "rating": "required|integer|min:1|max:5" # INTEGER NOT NULL + range validation
34
+ "slug": "required|string|regex:^[a-z0-9-]+$" # VARCHAR(255) NOT NULL + pattern validation
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Base Types
40
+
41
+ | Type | Default SQL Type | Description |
42
+ | ---------------- | ---------------- | ------------------------------ |
43
+ | `auto_increment` | SERIAL / INT AI | Auto-incrementing primary key |
44
+ | `string` | VARCHAR(255) | Text/string columns |
45
+ | `integer` | INTEGER | Whole number columns |
46
+ | `numeric` | DECIMAL(12,2) | Decimal/floating-point columns |
47
+ | `boolean` | BOOLEAN | True/false columns |
48
+ | `datetime` | TIMESTAMP | Date and time columns |
49
+ | `object` | JSON / JSONB | JSON data columns |
50
+
51
+ ---
52
+
53
+ ## Sub-Types (colon syntax)
54
+
55
+ Sub-types refine the SQL column type generated for each adapter. If no sub-type is specified, the default mapping is used.
56
+
57
+ ### String Sub-Types
58
+
59
+ | Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
60
+ | ------------ | --------------- | ------------ | ------- | ---------------- | ------------- |
61
+ | _(default)_ | VARCHAR(255) | VARCHAR(255) | TEXT | NVARCHAR(255) | VARCHAR2(255) |
62
+ | `text` | TEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
63
+ | `mediumtext` | MEDIUMTEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
64
+ | `longtext` | LONGTEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
65
+ | `char` | CHAR(255) | CHAR(255) | TEXT | NCHAR(255) | CHAR(255) |
66
+ | `char(N)` | CHAR(N) | CHAR(N) | TEXT | NCHAR(N) | CHAR(N) |
67
+ | `varchar(N)` | VARCHAR(N) | VARCHAR(N) | TEXT | NVARCHAR(N) | VARCHAR2(N) |
68
+ | `uuid` | CHAR(36) | UUID | TEXT | UNIQUEIDENTIFIER | CHAR(36) |
69
+
70
+ **Usage:**
71
+
72
+ ```json
73
+ "description": "string:text"
74
+ "content": "required|string:longtext"
75
+ "code": "string:char(10)"
76
+ "external_id": "string:uuid"
77
+ "title": "required|string:varchar(500)"
78
+ ```
79
+
80
+ ### Integer Sub-Types
81
+
82
+ | Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
83
+ | ----------------- | --------------- | ---------- | ------- | -------- | ---------- |
84
+ | _(default)_ | INT | INTEGER | INTEGER | INT | NUMBER(10) |
85
+ | `tinyint` | TINYINT | SMALLINT | INTEGER | TINYINT | NUMBER(3) |
86
+ | `smallint` | SMALLINT | SMALLINT | INTEGER | SMALLINT | NUMBER(5) |
87
+ | `bigint` | BIGINT | BIGINT | INTEGER | BIGINT | NUMBER(19) |
88
+ | `unsigned` | INT UNSIGNED | INTEGER | INTEGER | INT | NUMBER(10) |
89
+ | `bigint_unsigned` | BIGINT UNSIGNED | BIGINT | INTEGER | BIGINT | NUMBER(19) |
90
+
91
+ **Usage:**
92
+
93
+ ```json
94
+ "stock_quantity": "required|integer:unsigned"
95
+ "view_count": "integer:bigint"
96
+ "flags": "integer:tinyint"
97
+ "population": "integer:bigint_unsigned"
98
+ ```
99
+
100
+ ### Numeric Sub-Types
101
+
102
+ | Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
103
+ | -------------- | --------------- | ---------------- | ------- | ------------- | ------------- |
104
+ | _(default)_ | DECIMAL(12,2) | DECIMAL(12,2) | REAL | DECIMAL(12,2) | NUMBER(12,2) |
105
+ | `float` | FLOAT | REAL | REAL | FLOAT | FLOAT |
106
+ | `double` | DOUBLE | DOUBLE PRECISION | REAL | FLOAT | BINARY_DOUBLE |
107
+ | `decimal(P,S)` | DECIMAL(P,S) | DECIMAL(P,S) | REAL | DECIMAL(P,S) | NUMBER(P,S) |
108
+ | `money` | DECIMAL(19,4) | MONEY | REAL | MONEY | NUMBER(19,4) |
109
+
110
+ **Usage:**
111
+
112
+ ```json
113
+ "price": "required|numeric:decimal(10,4)"
114
+ "weight": "numeric:float"
115
+ "latitude": "numeric:double"
116
+ "balance": "required|numeric:money"
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Validation Rules
122
+
123
+ Validation rules are appended after the type (and optional sub-type) using the pipe `|` separator. These map directly to [node-input-validator](https://www.npmjs.com/package/node-input-validator) rules and are enforced at the API layer during insert/update operations.
124
+
125
+ ### Available Validators
126
+
127
+ | Validator | Description | Example |
128
+ | --------------------- | ---------------------------------------- | -------------------------------- |
129
+ | `email` | Must be a valid email address | `string\|email` |
130
+ | `phoneNumber` | Must be a valid phone number | `string\|phoneNumber` |
131
+ | `url` | Must be a valid URL | `string\|url` |
132
+ | `ip` | Must be a valid IP address | `string\|ip` |
133
+ | `minLength:N` | Minimum string length | `string\|minLength:3` |
134
+ | `maxLength:N` | Maximum string length | `string\|maxLength:100` |
135
+ | `lengthBetween:N1,N2` | String length must be between N1 and N2 | `string\|lengthBetween:3,50` |
136
+ | `min:N` | Minimum numeric value | `integer\|min:0` |
137
+ | `max:N` | Maximum numeric value | `integer\|max:100` |
138
+ | `between:N1,N2` | Numeric value must be between N1 and N2 | `integer\|between:1,5` |
139
+ | `regex:PATTERN` | Must match the regex pattern | `string\|regex:^[a-z0-9-]+$` |
140
+ | `alpha` | Only alphabetic characters | `string\|alpha` |
141
+ | `alphaNumeric` | Only alphanumeric characters | `string\|alphaNumeric` |
142
+ | `alphaDash` | Alphanumeric, dashes, and underscores | `string\|alphaDash` |
143
+ | `in:val1,val2,...` | Must be one of the listed values (enum) | `string\|in:active,inactive` |
144
+ | `notIn:val1,val2,...` | Must NOT be one of the listed values | `string\|notIn:banned,suspended` |
145
+ | `digits:N` | Must be exactly N digits | `string\|digits:6` |
146
+ | `digitsBetween:N1,N2` | Digit count must be between N1 and N2 | `string\|digitsBetween:4,8` |
147
+ | `dateFormat:FORMAT` | Must match date format (e.g. YYYY-MM-DD) | `string\|dateFormat:YYYY-MM-DD` |
148
+ | `json` | Must be valid JSON string | `string\|json` |
149
+ | `same:field` | Must match another field's value | `string\|same:password` |
150
+ | `different:field` | Must differ from another field's value | `string\|different:old_email` |
151
+
152
+ ### Validation Rule Parsing
153
+
154
+ The parser distinguishes between **type/sub-type tokens** and **validation tokens**:
155
+
156
+ 1. `required` — always a modifier (NOT NULL)
157
+ 2. First non-`required` token — the **base type** (string, integer, numeric, boolean, object, datetime, auto_increment)
158
+ 3. If the base type contains a colon `:` — the part after `:` is the **sub-type**
159
+ 4. All remaining pipe-separated tokens — **validation rules**
160
+
161
+ **Parsing example:**
162
+
163
+ ```
164
+ "required|string:text|minLength:10|maxLength:5000"
165
+ ```
166
+
167
+ | Token | Role |
168
+ | ---------------- | ---------- |
169
+ | `required` | Modifier |
170
+ | `string:text` | Type + Sub |
171
+ | `minLength:10` | Validator |
172
+ | `maxLength:5000` | Validator |
173
+
174
+ Result:
175
+
176
+ - SQL: `TEXT NOT NULL`
177
+ - Validation: `{ required: true, minLength: 10, maxLength: 5000 }`
178
+
179
+ ---
180
+
181
+ ## Full Example Schema
182
+
183
+ ```json
184
+ {
185
+ "adapter": "postgres",
186
+ "framework": "express",
187
+ "options": {
188
+ "session": "redis",
189
+ "rateLimiting": true,
190
+ "helmet": true,
191
+ "logger": true,
192
+ "loki": false
193
+ },
194
+ "tables": {
195
+ "products": {
196
+ "columns": {
197
+ "product_id": "auto_increment",
198
+ "category_id": "required|integer:unsigned",
199
+ "name": "required|string:varchar(300)|minLength:3|maxLength:300",
200
+ "slug": "required|string|regex:^[a-z0-9-]+$|maxLength:300",
201
+ "description": "string:text|maxLength:5000",
202
+ "short_description": "string:varchar(500)|maxLength:500",
203
+ "sku": "required|string|alphaNumeric|minLength:3|maxLength:50",
204
+ "price": "required|numeric:decimal(10,2)|min:0",
205
+ "compare_at_price": "numeric:decimal(10,2)|min:0",
206
+ "cost_price": "numeric:decimal(10,2)|min:0",
207
+ "currency": "required|string|minLength:3|maxLength:3",
208
+ "stock_quantity": "required|integer:unsigned|min:0",
209
+ "low_stock_threshold": "integer:unsigned|min:0",
210
+ "weight": "numeric:float|min:0",
211
+ "weight_unit": "string|in:kg,lb,oz,g",
212
+ "is_active": "boolean",
213
+ "is_featured": "boolean",
214
+ "is_deleted": "boolean",
215
+ "meta": "object",
216
+ "created_at": "datetime",
217
+ "modified_at": "datetime"
218
+ },
219
+ "pk": "product_id",
220
+ "unique": ["sku", "slug"],
221
+ "softDelete": "is_deleted",
222
+ "timestamps": {
223
+ "created_at": "created_at",
224
+ "modified_at": "modified_at"
225
+ },
226
+ "parent": null
227
+ },
228
+ "users": {
229
+ "columns": {
230
+ "user_id": "auto_increment",
231
+ "name": "required|string|minLength:2|maxLength:100",
232
+ "email": "required|string|email|maxLength:255",
233
+ "phone": "string|phoneNumber",
234
+ "password_hash": "required|string:varchar(500)",
235
+ "avatar_url": "string|url",
236
+ "bio": "string:text|maxLength:2000",
237
+ "age": "integer|min:13|max:150",
238
+ "role": "required|string|in:admin,user,moderator",
239
+ "login_count": "integer:unsigned|min:0",
240
+ "is_verified": "boolean",
241
+ "is_deleted": "boolean",
242
+ "last_login_ip": "string|ip",
243
+ "metadata": "object|json",
244
+ "created_at": "datetime",
245
+ "modified_at": "datetime"
246
+ },
247
+ "pk": "user_id",
248
+ "unique": ["email"],
249
+ "softDelete": "is_deleted",
250
+ "timestamps": {
251
+ "created_at": "created_at",
252
+ "modified_at": "modified_at"
253
+ },
254
+ "parent": null
255
+ },
256
+ "addresses": {
257
+ "columns": {
258
+ "address_id": "auto_increment",
259
+ "user_id": "required|integer:unsigned",
260
+ "label": "string|in:home,work,billing,shipping|maxLength:50",
261
+ "line1": "required|string|minLength:5|maxLength:255",
262
+ "line2": "string|maxLength:255",
263
+ "city": "required|string|minLength:2|maxLength:100",
264
+ "state": "required|string|minLength:2|maxLength:100",
265
+ "postal_code": "required|string|minLength:3|maxLength:20",
266
+ "country": "required|string|minLength:2|maxLength:2",
267
+ "is_default": "boolean",
268
+ "created_at": "datetime",
269
+ "modified_at": "datetime"
270
+ },
271
+ "pk": "address_id",
272
+ "unique": ["address_id"],
273
+ "timestamps": {
274
+ "created_at": "created_at",
275
+ "modified_at": "modified_at"
276
+ },
277
+ "parent": null
278
+ },
279
+ "orders": {
280
+ "columns": {
281
+ "order_id": "auto_increment",
282
+ "user_id": "required|integer:unsigned",
283
+ "order_number": "required|string|alphaDash|maxLength:50",
284
+ "status": "required|string|in:pending,processing,shipped,delivered,cancelled",
285
+ "subtotal": "required|numeric:money|min:0",
286
+ "tax_amount": "required|numeric:money|min:0",
287
+ "shipping_amount": "required|numeric:money|min:0",
288
+ "discount_amount": "numeric:money|min:0",
289
+ "total": "required|numeric:money|min:0",
290
+ "currency": "required|string|minLength:3|maxLength:3",
291
+ "notes": "string:text|maxLength:2000",
292
+ "created_at": "datetime",
293
+ "modified_at": "datetime"
294
+ },
295
+ "pk": "order_id",
296
+ "unique": ["order_number"],
297
+ "timestamps": {
298
+ "created_at": "created_at",
299
+ "modified_at": "modified_at"
300
+ },
301
+ "parent": null
302
+ }
303
+ }
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## How It Affects Code Generation
310
+
311
+ ### Migration Generation
312
+
313
+ The sub-type controls the SQL column type in generated migrations:
314
+
315
+ ```sql
316
+ -- "name": "required|string:varchar(300)|minLength:3|maxLength:300"
317
+ CREATE TABLE products (
318
+ name VARCHAR(300) NOT NULL,
319
+ ...
320
+ );
321
+
322
+ -- "description": "string:text|maxLength:5000"
323
+ CREATE TABLE products (
324
+ description TEXT,
325
+ ...
326
+ );
327
+
328
+ -- "stock_quantity": "required|integer:unsigned|min:0"
329
+ CREATE TABLE products (
330
+ stock_quantity INT UNSIGNED NOT NULL, -- MySQL
331
+ stock_quantity INTEGER NOT NULL, -- PostgreSQL (no unsigned)
332
+ ...
333
+ );
334
+ ```
335
+
336
+ ### Model Generation
337
+
338
+ Validation rules are extracted and passed to the model structure for runtime enforcement:
339
+
340
+ ```js
341
+ // Generated model structure
342
+ const products = model(
343
+ db,
344
+ "products",
345
+ {
346
+ name: "required|string|minLength:3|maxLength:300",
347
+ slug: "required|string|regex:^[a-z0-9-]+$|maxLength:300",
348
+ price: "required|numeric|min:0",
349
+ weight_unit: "string|in:kg,lb,oz,g",
350
+ // ...
351
+ },
352
+ "product_id",
353
+ ["sku", "slug"],
354
+ { safeDelete: "is_deleted" },
355
+ );
356
+ ```
357
+
358
+ The model passes these rules to `node-input-validator` on every insert/update/patch operation. Sub-types are stripped — only the base type and validators are kept in the runtime structure.
359
+
360
+ ### OpenAPI Generation
361
+
362
+ Validators map to OpenAPI schema properties:
363
+
364
+ | Validator | OpenAPI Property |
365
+ | --------------- | ------------------------------------- |
366
+ | `email` | `format: "email"` |
367
+ | `url` | `format: "uri"` |
368
+ | `ip` | `format: "ipv4"` |
369
+ | `phoneNumber` | `pattern: "^\\+?[0-9\\s\\-\\(\\)]+$"` |
370
+ | `minLength:N` | `minLength: N` |
371
+ | `maxLength:N` | `maxLength: N` |
372
+ | `min:N` | `minimum: N` |
373
+ | `max:N` | `maximum: N` |
374
+ | `between:N1,N2` | `minimum: N1, maximum: N2` |
375
+ | `in:a,b,c` | `enum: ["a", "b", "c"]` |
376
+ | `regex:PATTERN` | `pattern: "PATTERN"` |
377
+ | `alpha` | `pattern: "^[a-zA-Z]+$"` |
378
+ | `alphaNumeric` | `pattern: "^[a-zA-Z0-9]+$"` |
379
+ | `alphaDash` | `pattern: "^[a-zA-Z0-9_-]+$"` |
380
+
381
+ ---
382
+
383
+ ## Backward Compatibility
384
+
385
+ The new syntax is fully backward compatible:
386
+
387
+ - `"required|string"` — still works (VARCHAR(255) NOT NULL, no extra validation)
388
+ - `"integer"` — still works (INTEGER, nullable, no extra validation)
389
+ - `"string:text"` — new sub-type, no validation
390
+ - `"required|string|email"` — new validation, default sub-type
391
+ - `"required|string:text|minLength:10|maxLength:5000"` — full syntax
392
+
393
+ Existing schemas without sub-types or validators continue to work unchanged.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "db-model-router",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Generative API Creation using mysql2 and express libraries in node js",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -20,7 +20,8 @@
20
20
  "test:mssql": "dotenv -e env/.env.mssql -- mocha test/adapters/mssql.*.test.js --timeout 30000 --exit",
21
21
  "test:kafka": "dotenv -e env/.env.kafka -- mocha test/kafka.test.js test/kafka.integration.test.js --timeout 30000 --exit",
22
22
  "test:properties": "mocha test/properties/*.property.test.js --timeout 30000 --exit",
23
- "test:all": "mocha test/adapters/*.test.js test/properties/*.property.test.js test/function.test.js --timeout 30000 --exit",
23
+ "test:all": "mocha test/adapters/*.test.js test/properties/*.property.test.js test/function.test.js test/commands/*.test.js --timeout 300000 --exit",
24
+ "test:command": "mocha test/commands/*.test.js --timeout 30000 --exit",
24
25
  "demo:clear": "node -e \"var fs=require('fs'),p=require('path'),d=p.join(__dirname,'demo');fs.existsSync(d)&&fs.rmSync(d,{recursive:true,force:true});fs.mkdirSync(d,{recursive:true})\"",
25
26
  "demo:create": "node scripts/demo-create.js"
26
27
  },
@@ -109,6 +110,7 @@
109
110
  }
110
111
  },
111
112
  "devDependencies": {
113
+ "@faker-js/faker": "^10.4.0",
112
114
  "dotenv-cli": "^11.0.0",
113
115
  "express": "^4.21.0",
114
116
  "faker": "^5.5.3",
package/skill/SKILL.md CHANGED
@@ -97,7 +97,7 @@ const users = model(
97
97
  {
98
98
  safeDelete: "is_deleted", // soft-delete column
99
99
  created_at: "created_at", // auto-managed timestamp
100
- modified_at: "updated_at", // auto-managed timestamp
100
+ modified_at: "modified_at", // auto-managed timestamp
101
101
  },
102
102
  );
103
103
 
@@ -343,12 +343,15 @@ Requires a `.env` file with `DB_TYPE` and connection variables.
343
343
  "email": "required|string",
344
344
  "is_deleted": "boolean",
345
345
  "created_at": "datetime",
346
- "updated_at": "datetime"
346
+ "modified_at": "datetime"
347
347
  },
348
348
  "pk": "user_id",
349
349
  "unique": ["email"],
350
350
  "softDelete": "is_deleted",
351
- "timestamps": { "created_at": "created_at", "modified_at": "updated_at" },
351
+ "timestamps": {
352
+ "created_at": "created_at",
353
+ "modified_at": "modified_at"
354
+ },
352
355
  "parent": null
353
356
  },
354
357
  "posts": {
@@ -385,10 +388,50 @@ Requires a `.env` file with `DB_TYPE` and connection variables.
385
388
 
386
389
  ### Column Rules
387
390
 
388
- Format: `(required|)?(string|integer|numeric|boolean|object|datetime|auto_increment)`
391
+ Format: `[required|]<type>[:<subtype>][|<validator>...]`
389
392
 
390
393
  Include ALL columns in schema (PK, timestamps, softDelete). The generator auto-excludes them from model `structure`.
391
394
 
395
+ **Sub-types** (colon syntax) refine the SQL column type:
396
+
397
+ | Base Type | Sub-Types | Default |
398
+ | --------- | ------------------------------------------------------------------------- | --------------- |
399
+ | `string` | `text`, `mediumtext`, `longtext`, `char`, `char(N)`, `varchar(N)`, `uuid` | `varchar(255)` |
400
+ | `integer` | `tinyint`, `smallint`, `bigint`, `unsigned`, `bigint_unsigned` | `int` |
401
+ | `numeric` | `float`, `double`, `decimal(P,S)`, `money` | `decimal(12,2)` |
402
+
403
+ **Validators** (pipe-separated, node-input-validator compatible):
404
+
405
+ | Validator | Description | Example |
406
+ | ------------------ | ---------------------------------- | ---------------------------- |
407
+ | `email` | Valid email format | `string\|email` |
408
+ | `phoneNumber` | Valid phone number | `string\|phoneNumber` |
409
+ | `url` | Valid URL | `string\|url` |
410
+ | `ip` | Valid IP address | `string\|ip` |
411
+ | `minLength:N` | Min string length | `string\|minLength:3` |
412
+ | `maxLength:N` | Max string length | `string\|maxLength:100` |
413
+ | `min:N` | Min numeric value | `integer\|min:0` |
414
+ | `max:N` | Max numeric value | `integer\|max:100` |
415
+ | `between:N1,N2` | Numeric range | `integer\|between:1,5` |
416
+ | `regex:PATTERN` | Regex pattern match | `string\|regex:^[a-z0-9-]+$` |
417
+ | `in:val1,val2,...` | Enum (must be one of) | `string\|in:active,inactive` |
418
+ | `alpha` | Only letters | `string\|alpha` |
419
+ | `alphaNumeric` | Only letters + numbers | `string\|alphaNumeric` |
420
+ | `alphaDash` | Letters, numbers, dash, underscore | `string\|alphaDash` |
421
+
422
+ **Full examples:**
423
+
424
+ ```
425
+ "email": "required|string|email|maxLength:255"
426
+ "description": "string:text|maxLength:5000"
427
+ "price": "required|numeric:decimal(10,2)|min:0"
428
+ "stock": "required|integer:unsigned|min:0"
429
+ "role": "required|string|in:admin,user,moderator"
430
+ "slug": "required|string|regex:^[a-z0-9-]+$|maxLength:300"
431
+ ```
432
+
433
+ For the full specification, see `docs/dbmr-schema-spec.md`.
434
+
392
435
  ### Table Fields
393
436
 
394
437
  | Field | Required | Description |
@@ -97,14 +97,35 @@ async function generate(args, flags, ctx) {
97
97
  const relationships = schema.relationships || [];
98
98
  const tableNames = meta.map((m) => m.table).sort();
99
99
 
100
- // Determine which artifact types to generate
101
- // All flags default to true use --flag=false to disable
102
- const genModels = args.models !== false;
103
- const genRoutes = args.routes !== false;
104
- const genOpenapi = args.openapi !== false;
105
- const genTests = args.tests !== false;
106
- const genMigrations = args.migrations !== false;
107
- const genSaas = args["saas-structure"] !== false;
100
+ // Determine which artifact types to generate.
101
+ // If any specific artifact flag is explicitly set to true, only generate those.
102
+ // Otherwise, all artifact types are generated (unless explicitly set to false).
103
+ const hasExplicitTrue =
104
+ args.models === true ||
105
+ args.routes === true ||
106
+ args.openapi === true ||
107
+ args.tests === true ||
108
+ args.migrations === true;
109
+
110
+ let genModels, genRoutes, genOpenapi, genTests, genMigrations, genSaas;
111
+
112
+ if (hasExplicitTrue) {
113
+ // Selective mode: only generate what was explicitly requested
114
+ genModels = args.models === true;
115
+ genRoutes = args.routes === true;
116
+ genOpenapi = args.openapi === true;
117
+ genTests = args.tests === true;
118
+ genMigrations = args.migrations === true;
119
+ genSaas = args["saas-structure"] === true;
120
+ } else {
121
+ // Default mode: generate all unless explicitly disabled
122
+ genModels = args.models !== false;
123
+ genRoutes = args.routes !== false;
124
+ genOpenapi = args.openapi !== false;
125
+ genTests = args.tests !== false;
126
+ genMigrations = args.migrations !== false;
127
+ genSaas = args["saas-structure"] !== false;
128
+ }
108
129
 
109
130
  const baseDir = process.cwd();
110
131
 
@@ -228,7 +249,7 @@ async function generate(args, flags, ctx) {
228
249
  if (nestedChildrenForTests.has(m.table)) continue;
229
250
  planned.push({
230
251
  relPath: `test/${m.table}.test.js`,
231
- content: generateTestFile(m.table, m.primary_key),
252
+ content: generateTestFile(m.table, m.primary_key, m.structure),
232
253
  });
233
254
  }
234
255
 
@@ -286,6 +307,15 @@ async function generate(args, flags, ctx) {
286
307
  }
287
308
 
288
309
  for (const entry of saasFiles) {
310
+ // Skip seed/credential files if they already exist on disk —
311
+ // they contain generated passwords that should not be overwritten.
312
+ if (
313
+ (entry.relPath === "seeds/saas-seed.js" ||
314
+ entry.relPath === "credentials.md") &&
315
+ fs.existsSync(path.join(baseDir, entry.relPath))
316
+ ) {
317
+ continue;
318
+ }
289
319
  planned.push(entry);
290
320
  }
291
321
  }