db-model-router 1.0.10 → 1.0.11
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/.codegraph/config.json +143 -0
- package/CLAUDE.md +150 -0
- package/dbmr.schema.json +3 -0
- package/docs/dbmr-schema-spec.md +597 -393
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/dbmr-schema-spec.md +597 -0
- package/src/cli/generate-migration.js +4 -0
- package/src/schema/schema-validator.js +1 -1
- package/.env +0 -7
- package/db-manager/.dbmanager.sqlite +0 -0
- package/db-manager/.dbmanager.sqlite-shm +0 -0
- package/db-manager/.dbmanager.sqlite-wal +0 -0
- package/db-manager/demo/cockroachdb.env +0 -6
- package/db-manager/demo/demo.sqlite +0 -0
- package/db-manager/demo/dynamodb.env +0 -7
- package/db-manager/demo/mongodb.env +0 -4
- package/db-manager/demo/mssql.env +0 -6
- package/db-manager/demo/mysql.env +0 -6
- package/db-manager/demo/oracle.env +0 -6
- package/db-manager/demo/postgres.env +0 -6
- package/db-manager/demo/redis.env +0 -4
- package/db-manager/demo/seeds/cockroachdb.sql +0 -32
- package/db-manager/demo/seeds/mssql.sql +0 -32
- package/db-manager/demo/seeds/mysql.sql +0 -32
- package/db-manager/demo/seeds/oracle.sql +0 -43
- package/db-manager/demo/seeds/postgres.sql +0 -32
- package/db-manager/demo/seeds/sqlite3.sql +0 -32
- package/db-manager/demo/sqlite3.env +0 -2
- package/demo/.dockerignore +0 -7
- package/demo/.env.example +0 -15
- package/demo/Dockerfile +0 -20
- package/demo/app.js +0 -39
- package/demo/commons/add_migration.js +0 -43
- package/demo/commons/db.js +0 -17
- package/demo/commons/migrate.js +0 -68
- package/demo/commons/modules.js +0 -18
- package/demo/commons/password.js +0 -36
- package/demo/commons/security.js +0 -30
- package/demo/commons/session.js +0 -13
- package/demo/commons/webhook.js +0 -81
- package/demo/dbmr.schema.json +0 -338
- package/demo/middleware/authenticate.js +0 -14
- package/demo/middleware/hasPermission.js +0 -30
- package/demo/middleware/logger.js +0 -67
- package/demo/middleware/tenantIsolation.js +0 -19
- package/demo/migrations/20260518204325_create_migrations_table.sql +0 -6
- package/demo/migrations/20260518204325_create_saas_tables.sql +0 -69
- package/demo/migrations/20260518204325_create_tables.sql +0 -193
- package/demo/models/addresses.js +0 -24
- package/demo/models/cart_items.js +0 -20
- package/demo/models/carts.js +0 -18
- package/demo/models/categories.js +0 -22
- package/demo/models/coupons.js +0 -25
- package/demo/models/index.js +0 -43
- package/demo/models/order_items.js +0 -23
- package/demo/models/orders.js +0 -27
- package/demo/models/payments.js +0 -23
- package/demo/models/product_images.js +0 -20
- package/demo/models/product_reviews.js +0 -22
- package/demo/models/product_variants.js +0 -22
- package/demo/models/products.js +0 -32
- package/demo/models/role_permissions.js +0 -17
- package/demo/models/roles.js +0 -17
- package/demo/models/shipments.js +0 -21
- package/demo/models/tenants.js +0 -18
- package/demo/models/users.js +0 -23
- package/demo/models/webhook_logs.js +0 -22
- package/demo/models/webhooks.js +0 -19
- package/demo/models/wishlists.js +0 -17
- package/demo/openapi.json +0 -7000
- package/demo/package-lock.json +0 -3989
- package/demo/package.json +0 -46
- package/demo/routes/addresses/index.js +0 -10
- package/demo/routes/auth/index.js +0 -55
- package/demo/routes/carts/cart_items/index.js +0 -11
- package/demo/routes/carts/index.js +0 -14
- package/demo/routes/categories/index.js +0 -10
- package/demo/routes/coupons/index.js +0 -10
- package/demo/routes/docs.js +0 -18
- package/demo/routes/health.js +0 -35
- package/demo/routes/index.js +0 -40
- package/demo/routes/orders/index.js +0 -18
- package/demo/routes/orders/order_items/index.js +0 -11
- package/demo/routes/orders/payments/index.js +0 -11
- package/demo/routes/orders/shipments/index.js +0 -11
- package/demo/routes/products/index.js +0 -18
- package/demo/routes/products/product_images/index.js +0 -11
- package/demo/routes/products/product_reviews/index.js +0 -11
- package/demo/routes/products/product_variants/index.js +0 -11
- package/demo/routes/roles/index.js +0 -75
- package/demo/routes/roles/permissions/index.js +0 -47
- package/demo/routes/tenants/index.js +0 -45
- package/demo/routes/users/index.js +0 -45
- package/demo/routes/wishlists/index.js +0 -10
- package/demo/seeds/saas-seed.js +0 -329
package/docs/dbmr-schema-spec.md
CHANGED
|
@@ -1,393 +1,597 @@
|
|
|
1
|
-
# dbmr.schema.json —
|
|
2
|
-
|
|
3
|
-
This document
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
| `
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
1
|
+
# dbmr.schema.json — Complete Authoritative Specification
|
|
2
|
+
|
|
3
|
+
This document is the single source of truth for writing a `dbmr.schema.json` file. It covers the top-level structure, every table field, the full column rule syntax, how nested object validation works, and which tables are auto-generated by the SaaS structure so you never duplicate them. It also explains how each part of the schema drives code generation (migrations, models, OpenAPI).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Top-Level Structure
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"adapter": "postgres",
|
|
12
|
+
"framework": "express",
|
|
13
|
+
"options": {
|
|
14
|
+
"session": "redis",
|
|
15
|
+
"rateLimiting": true,
|
|
16
|
+
"helmet": true,
|
|
17
|
+
"logger": true,
|
|
18
|
+
"loki": false
|
|
19
|
+
},
|
|
20
|
+
"tables": { ... },
|
|
21
|
+
"relationships": [ ... ]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Field | Required | Description |
|
|
26
|
+
| --------------- | -------- | ------------------------------------------------------------------------------ |
|
|
27
|
+
| `adapter` | Yes | Database adapter. One of: `mysql`, `mariadb`, `postgres`, `sqlite3`, `mongodb`, `mssql`, `cockroachdb`, `oracle`, `redis`, `dynamodb` |
|
|
28
|
+
| `framework` | Yes | Express variant. One of: `express`, `ultimate-express` |
|
|
29
|
+
| `options` | No | Project scaffolding options (session store, rate limiting, helmet, logger, loki) |
|
|
30
|
+
| `tables` | Yes | Object where each key is a table name and the value is a **Table Definition** |
|
|
31
|
+
| `relationships` | No | Array of parent-child route nesting descriptors |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Table Definition
|
|
36
|
+
|
|
37
|
+
Each entry in `tables` is a Table Definition object:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"columns": { ... },
|
|
42
|
+
"pk": "product_id",
|
|
43
|
+
"unique": ["sku"],
|
|
44
|
+
"softDelete": "is_deleted",
|
|
45
|
+
"timestamps": {
|
|
46
|
+
"created_at": "created_at",
|
|
47
|
+
"modified_at": "modified_at"
|
|
48
|
+
},
|
|
49
|
+
"parent": null
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Field | Required | Description |
|
|
54
|
+
| ------------ | -------- | --------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `columns` | Yes | **All** columns in the table, including PK, timestamps, and soft-delete. Each key is a column name; the value is a **Column Rule** string. |
|
|
56
|
+
| `pk` | Yes | Primary key column name. Convention: `<table>_id` (e.g. `user_id`, `order_id`). |
|
|
57
|
+
| `unique` | No | Array of column names with unique constraints. Defaults to `[pk]` if omitted. |
|
|
58
|
+
| `softDelete` | No | Column name used for soft-delete. When set, `remove()` updates this column to `1`/`true` instead of hard-deleting. |
|
|
59
|
+
| `timestamps` | No | Object mapping `{ created_at: "col_name", modified_at: "col_name" }`. These columns are auto-excluded from insert/update payloads. |
|
|
60
|
+
| `parent` | No | Parent table name for route nesting, or `null` for a top-level route. |
|
|
61
|
+
|
|
62
|
+
> **Important:** `columns` must list **every** column in the table, even ones that are auto-managed (PK, timestamps, soft-delete). The generator knows to exclude them from the runtime model structure, but they must be present for migration and introspection generation.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Column Rules
|
|
67
|
+
|
|
68
|
+
Column rules are pipe-delimited strings that describe the data type, sub-type, and validation constraints.
|
|
69
|
+
|
|
70
|
+
### Syntax
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
[required|]<type>[:<subtype>][|<validator>[|<validator>...]]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Parts:**
|
|
77
|
+
|
|
78
|
+
| Part | Required | Description |
|
|
79
|
+
| ----------- | -------- | ---------------------------------------------------- |
|
|
80
|
+
| `required` | No | Prefix — marks column as NOT NULL |
|
|
81
|
+
| `type` | Yes | Base data type (see Base Types table) |
|
|
82
|
+
| `:subtype` | No | Refines the SQL column type for migrations |
|
|
83
|
+
| `validator` | No | Runtime validation rules (node-input-validator) |
|
|
84
|
+
|
|
85
|
+
### Base Types
|
|
86
|
+
|
|
87
|
+
| Type | Default SQL Type | Description |
|
|
88
|
+
| ---------------- | ---------------- | ------------------------------------ |
|
|
89
|
+
| `auto_increment` | SERIAL / INT AI | Auto-incrementing primary key |
|
|
90
|
+
| `string` | VARCHAR(255) | Text/string columns |
|
|
91
|
+
| `integer` | INTEGER | Whole number columns |
|
|
92
|
+
| `numeric` | DECIMAL(12,2) | Decimal/floating-point columns |
|
|
93
|
+
| `boolean` | BOOLEAN | True/false columns |
|
|
94
|
+
| `datetime` | TIMESTAMP | Date and time columns |
|
|
95
|
+
| `object` | JSON / JSONB | JSON data columns |
|
|
96
|
+
|
|
97
|
+
### Sub-Types (colon syntax)
|
|
98
|
+
|
|
99
|
+
Sub-types refine the SQL column type generated for each adapter. If no sub-type is specified, the default mapping is used.
|
|
100
|
+
|
|
101
|
+
#### String Sub-Types
|
|
102
|
+
|
|
103
|
+
| Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
|
|
104
|
+
| ------------ | --------------- | ------------ | ------- | ---------------- | ------------- |
|
|
105
|
+
| _(default)_ | VARCHAR(255) | VARCHAR(255) | TEXT | NVARCHAR(255) | VARCHAR2(255) |
|
|
106
|
+
| `text` | TEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
|
|
107
|
+
| `mediumtext` | MEDIUMTEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
|
|
108
|
+
| `longtext` | LONGTEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
|
|
109
|
+
| `char` | CHAR(255) | CHAR(255) | TEXT | NCHAR(255) | CHAR(255) |
|
|
110
|
+
| `char(N)` | CHAR(N) | CHAR(N) | TEXT | NCHAR(N) | CHAR(N) |
|
|
111
|
+
| `varchar(N)` | VARCHAR(N) | VARCHAR(N) | TEXT | NVARCHAR(N) | VARCHAR2(N) |
|
|
112
|
+
| `uuid` | CHAR(36) | UUID | TEXT | UNIQUEIDENTIFIER | CHAR(36) |
|
|
113
|
+
|
|
114
|
+
**Usage:**
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
"description": "string:text"
|
|
118
|
+
"content": "required|string:longtext"
|
|
119
|
+
"code": "string:char(10)"
|
|
120
|
+
"external_id": "string:uuid"
|
|
121
|
+
"title": "required|string:varchar(500)"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Integer Sub-Types
|
|
125
|
+
|
|
126
|
+
| Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
|
|
127
|
+
| ----------------- | --------------- | ---------- | ------- | -------- | ---------- |
|
|
128
|
+
| _(default)_ | INT | INTEGER | INTEGER | INT | NUMBER(10) |
|
|
129
|
+
| `tinyint` | TINYINT | SMALLINT | INTEGER | TINYINT | NUMBER(3) |
|
|
130
|
+
| `smallint` | SMALLINT | SMALLINT | INTEGER | SMALLINT | NUMBER(5) |
|
|
131
|
+
| `bigint` | BIGINT | BIGINT | INTEGER | BIGINT | NUMBER(19) |
|
|
132
|
+
| `unsigned` | INT UNSIGNED | INTEGER | INTEGER | INT | NUMBER(10) |
|
|
133
|
+
| `bigint_unsigned` | BIGINT UNSIGNED | BIGINT | INTEGER | BIGINT | NUMBER(19) |
|
|
134
|
+
|
|
135
|
+
**Usage:**
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
"stock_quantity": "required|integer:unsigned"
|
|
139
|
+
"view_count": "integer:bigint"
|
|
140
|
+
"flags": "integer:tinyint"
|
|
141
|
+
"population": "integer:bigint_unsigned"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Numeric Sub-Types
|
|
145
|
+
|
|
146
|
+
| Sub-Type | MySQL / MariaDB | PostgreSQL | SQLite3 | MSSQL | Oracle |
|
|
147
|
+
| -------------- | --------------- | ---------------- | ------- | ------------- | ------------- |
|
|
148
|
+
| _(default)_ | DECIMAL(12,2) | DECIMAL(12,2) | REAL | DECIMAL(12,2) | NUMBER(12,2) |
|
|
149
|
+
| `float` | FLOAT | REAL | REAL | FLOAT | FLOAT |
|
|
150
|
+
| `double` | DOUBLE | DOUBLE PRECISION | REAL | FLOAT | BINARY_DOUBLE |
|
|
151
|
+
| `decimal(P,S)` | DECIMAL(P,S) | DECIMAL(P,S) | REAL | DECIMAL(P,S) | NUMBER(P,S) |
|
|
152
|
+
| `money` | DECIMAL(19,4) | MONEY | REAL | MONEY | NUMBER(19,4) |
|
|
153
|
+
|
|
154
|
+
**Usage:**
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
"price": "required|numeric:decimal(10,2)"
|
|
158
|
+
"weight": "numeric:float"
|
|
159
|
+
"latitude": "numeric:double"
|
|
160
|
+
"balance": "required|numeric:money"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Validation Rules
|
|
164
|
+
|
|
165
|
+
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/patch operations.
|
|
166
|
+
|
|
167
|
+
| Validator | Description | Example |
|
|
168
|
+
| --------------------- | --------------------------------------- | -------------------------------- |
|
|
169
|
+
| `email` | Valid email address | `string\|email` |
|
|
170
|
+
| `phoneNumber` | Valid phone number | `string\|phoneNumber` |
|
|
171
|
+
| `url` | Valid URL | `string\|url` |
|
|
172
|
+
| `ip` | Valid IP address | `string\|ip` |
|
|
173
|
+
| `minLength:N` | Minimum string length | `string\|minLength:3` |
|
|
174
|
+
| `maxLength:N` | Maximum string length | `string\|maxLength:100` |
|
|
175
|
+
| `lengthBetween:N1,N2` | String length between N1 and N2 | `string\|lengthBetween:3,50` |
|
|
176
|
+
| `min:N` | Minimum numeric value | `integer\|min:0` |
|
|
177
|
+
| `max:N` | Maximum numeric value | `integer\|max:100` |
|
|
178
|
+
| `between:N1,N2` | Numeric value between N1 and N2 | `integer\|between:1,5` |
|
|
179
|
+
| `regex:PATTERN` | Regex pattern match | `string\|regex:^[a-z0-9-]+$` |
|
|
180
|
+
| `alpha` | Only alphabetic | `string\|alpha` |
|
|
181
|
+
| `alphaNumeric` | Alphanumeric | `string\|alphaNumeric` |
|
|
182
|
+
| `alphaDash` | Alphanumeric, dash, underscore | `string\|alphaDash` |
|
|
183
|
+
| `in:val1,val2` | Enum | `string\|in:active,inactive` |
|
|
184
|
+
| `notIn:val1,val2` | Excluded enum | `string\|notIn:banned` |
|
|
185
|
+
| `digits:N` | Exactly N digits | `string\|digits:6` |
|
|
186
|
+
| `digitsBetween:N1,N2` | Digits between N1 and N2 | `string\|digitsBetween:4,8` |
|
|
187
|
+
| `dateFormat:FORMAT` | Date format match | `string\|dateFormat:YYYY-MM-DD` |
|
|
188
|
+
| `json` | Valid JSON string | `string\|json` |
|
|
189
|
+
| `same:field` | Must match another field | `string\|same:password` |
|
|
190
|
+
| `different:field` | Must differ from another field | `string\|different:old_email` |
|
|
191
|
+
|
|
192
|
+
### Validation Rule Parsing
|
|
193
|
+
|
|
194
|
+
The parser distinguishes between **type/sub-type tokens** and **validation tokens**:
|
|
195
|
+
|
|
196
|
+
1. `required` — always a modifier (NOT NULL)
|
|
197
|
+
2. First non-`required` token — the **base type** (string, integer, numeric, boolean, object, datetime, auto_increment)
|
|
198
|
+
3. If the base type contains a colon `:` — the part after `:` is the **sub-type**
|
|
199
|
+
4. All remaining pipe-separated tokens — **validation rules**
|
|
200
|
+
|
|
201
|
+
**Parsing example:**
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
"required|string:text|minLength:10|maxLength:5000"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Token | Role |
|
|
208
|
+
| ---------------- | ---------- |
|
|
209
|
+
| `required` | Modifier |
|
|
210
|
+
| `string:text` | Type + Sub |
|
|
211
|
+
| `minLength:10` | Validator |
|
|
212
|
+
| `maxLength:5000` | Validator |
|
|
213
|
+
|
|
214
|
+
Result:
|
|
215
|
+
|
|
216
|
+
- SQL: `TEXT NOT NULL`
|
|
217
|
+
- Validation: `{ required: true, minLength: 10, maxLength: 5000 }`
|
|
218
|
+
|
|
219
|
+
### Full Column Rule Examples
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"product_id": "auto_increment",
|
|
224
|
+
"name": "required|string|minLength:3|maxLength:300",
|
|
225
|
+
"slug": "required|string|regex:^[a-z0-9-]+$|maxLength:300",
|
|
226
|
+
"description": "string:text|maxLength:5000",
|
|
227
|
+
"price": "required|numeric:decimal(10,2)|min:0",
|
|
228
|
+
"stock": "required|integer:unsigned|min:0",
|
|
229
|
+
"is_active": "boolean",
|
|
230
|
+
"metadata": "object",
|
|
231
|
+
"created_at": "datetime"
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Object Columns and Nested Key Validation
|
|
238
|
+
|
|
239
|
+
### How Object Columns Work in the Database
|
|
240
|
+
|
|
241
|
+
When a column is declared as `object`, the migration generator creates **exactly one** JSON-compatible column in the database:
|
|
242
|
+
|
|
243
|
+
| Adapter | SQL Type Created |
|
|
244
|
+
| ------------- | ---------------- |
|
|
245
|
+
| `postgres` | `JSONB` |
|
|
246
|
+
| `cockroachdb` | `JSONB` |
|
|
247
|
+
| `mysql` | `JSON` |
|
|
248
|
+
| `mariadb` | `JSON` |
|
|
249
|
+
| `sqlite3` | `TEXT` |
|
|
250
|
+
| `mssql` | `NVARCHAR(MAX)` |
|
|
251
|
+
| `oracle` | `CLOB` |
|
|
252
|
+
| `mongodb` | Native document |
|
|
253
|
+
| `dynamodb` | Map attribute |
|
|
254
|
+
| `redis` | Hash field |
|
|
255
|
+
|
|
256
|
+
> **Critical:** The database table only contains the parent column (e.g., `config`). It does **not** create separate columns for sub-keys like `config.primary`, `config.secondary`, etc. The entire nested object is stored as a single JSON value.
|
|
257
|
+
|
|
258
|
+
### Sub-Key Validation at the Model Layer
|
|
259
|
+
|
|
260
|
+
While the database stores the object as a single JSON column, the runtime model can still validate individual keys inside the object using **dot-notation paths**. The validator (`node-input-validator`) supports nested path validation, so the generated model structure can include rules like:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
const products = model(
|
|
264
|
+
db,
|
|
265
|
+
"products",
|
|
266
|
+
{
|
|
267
|
+
name: "required|string|maxLength:300",
|
|
268
|
+
config: "object",
|
|
269
|
+
"config.primary": "required|string",
|
|
270
|
+
"config.secondary": "required|string",
|
|
271
|
+
"config.border_color": "string|regex:^[#a-fA-F0-9]+$",
|
|
272
|
+
},
|
|
273
|
+
"product_id",
|
|
274
|
+
["sku"],
|
|
275
|
+
{ safeDelete: "is_deleted" },
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
In the schema file, this is expressed by placing dot-notation keys alongside the parent `object` column in the `columns` definition:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"columns": {
|
|
284
|
+
"config": "object",
|
|
285
|
+
"config.primary": "required|string",
|
|
286
|
+
"config.secondary": "required|string",
|
|
287
|
+
"config.border_color": "string|regex:^[#a-fA-F0-9]+$"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**What the generator does with these entries:**
|
|
293
|
+
|
|
294
|
+
- **Migration (`generate-migration.js`):** Only the parent key (`config`) produces a database column. Dot-notation keys are **ignored** during table creation — they exist purely for runtime validation.
|
|
295
|
+
- **Model (`schema-to-meta.js`):** Both the parent (`config: "object"`) and the dot-notation keys (`config.primary: "required|string"`) are included in the generated model structure, so the API validates nested fields on every insert/update.
|
|
296
|
+
- **OpenAPI (`generate-openapi.js`):** The parent `config` is emitted as an `object` schema. Dot-notation keys are flattened into the same schema object with their types and constraints.
|
|
297
|
+
|
|
298
|
+
### Rules for Object Sub-Keys
|
|
299
|
+
|
|
300
|
+
1. The parent key **must** be declared as `object` (or `required|object`).
|
|
301
|
+
2. Sub-keys use dot notation relative to the parent: `config.primary`, `config.secondary`.
|
|
302
|
+
3. Sub-key rules follow the same syntax as top-level column rules (`required|`, type, validators), but the **type** is still required for validation clarity even though the DB column is already JSON.
|
|
303
|
+
4. You can nest arbitrarily deep: `settings.theme.dark_mode.primary_color`.
|
|
304
|
+
5. Sub-keys are **never** created as separate SQL columns. They are validation-only entries.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## SaaS Auto-Generated Tables
|
|
309
|
+
|
|
310
|
+
When you run `db-model-router generate` (or `db-model-router init`), the **SaaS structure is generated by default** unless you explicitly disable it with `--saas-structure=false`.
|
|
311
|
+
|
|
312
|
+
The following tables, middleware, routes, and seeds are scaffolded automatically. **Do NOT add these tables to your `dbmr.schema.json`** — doing so will cause duplication errors.
|
|
313
|
+
|
|
314
|
+
### Auto-Generated Tables
|
|
315
|
+
|
|
316
|
+
| Table | Description |
|
|
317
|
+
| ------------------ | ---------------------------------------------------------- |
|
|
318
|
+
| `tenants` | Multi-tenant root entity (tenant name, domain, settings) |
|
|
319
|
+
| `users` | User accounts with password hash, role, tenant linkage |
|
|
320
|
+
| `roles` | Role definitions (e.g. Admin, User, Moderator) |
|
|
321
|
+
| `role_permissions` | Junction table mapping roles to permission strings |
|
|
322
|
+
| `webhooks` | Outgoing webhook subscriptions per tenant |
|
|
323
|
+
| `webhook_logs` | Delivery attempt logs for webhooks |
|
|
324
|
+
|
|
325
|
+
### Auto-Generated Middleware
|
|
326
|
+
|
|
327
|
+
| File | Purpose |
|
|
328
|
+
| ------------------------- | ------------------------------------------------ |
|
|
329
|
+
| `middleware/authenticate.js` | JWT / session validation |
|
|
330
|
+
| `middleware/tenantIsolation.js` | Injects `tenant_id` from user into all queries |
|
|
331
|
+
| `middleware/hasPermission.js` | RBAC check against `role_permissions` |
|
|
332
|
+
|
|
333
|
+
### Auto-Generated Routes
|
|
334
|
+
|
|
335
|
+
| Route | Description |
|
|
336
|
+
| ---------------------------------- | ------------------------------------ |
|
|
337
|
+
| `POST /api/auth/login` | Login (returns JWT or session) |
|
|
338
|
+
| `POST /api/auth/logout` | Logout |
|
|
339
|
+
| `GET /api/users` | User CRUD |
|
|
340
|
+
| `GET /api/tenants` | Tenant CRUD |
|
|
341
|
+
| `GET /api/roles` | Role CRUD |
|
|
342
|
+
| `GET /api/roles/:role_id/permissions` | Permission management for a role |
|
|
343
|
+
|
|
344
|
+
### Auto-Generated Utilities
|
|
345
|
+
|
|
346
|
+
| File | Purpose |
|
|
347
|
+
| ------------------------ | ------------------------------------------------ |
|
|
348
|
+
| `commons/password.js` | `crypto.scrypt` password hashing |
|
|
349
|
+
| `commons/modules.js` | Registry of all generated models |
|
|
350
|
+
| `commons/webhook.js` | Webhook delivery with retry logic |
|
|
351
|
+
| `seeds/saas-seed.js` | Super Admin user + Tenant Admin role template |
|
|
352
|
+
|
|
353
|
+
### What This Means for Your Schema
|
|
354
|
+
|
|
355
|
+
**Only define your product/domain-specific tables in `dbmr.schema.json`.** Examples of tables that belong in your schema:
|
|
356
|
+
|
|
357
|
+
- `products`, `orders`, `order_items`
|
|
358
|
+
- `projects`, `tasks`, `comments`
|
|
359
|
+
- `invoices`, `payments`, `subscriptions`
|
|
360
|
+
- `categories`, `tags`, `articles`
|
|
361
|
+
|
|
362
|
+
Tables that must **never** appear in your schema (because SaaS generates them):
|
|
363
|
+
|
|
364
|
+
- `users`, `tenants`, `roles`, `role_permissions`
|
|
365
|
+
|
|
366
|
+
> **Note:** Foreign key columns referencing SaaS tables (e.g., `user_id`, `tenant_id`) **can** and **should** appear in your schema tables. They are ordinary integer columns. Just don't define the `users` table itself.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Relationships and Route Nesting
|
|
371
|
+
|
|
372
|
+
The `relationships` array defines parent-child route nesting independent of foreign key columns.
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
"relationships": [
|
|
376
|
+
{
|
|
377
|
+
"parent": "posts",
|
|
378
|
+
"child": "comments",
|
|
379
|
+
"foreignKey": "post_id"
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
| Field | Description |
|
|
385
|
+
| ------------ | -------------------------------------------------------------- |
|
|
386
|
+
| `parent` | Parent table name. Must exist in `tables`. |
|
|
387
|
+
| `child` | Child table name. Must exist in `tables`. |
|
|
388
|
+
| `foreignKey` | Column in the child table that references the parent's PK. |
|
|
389
|
+
|
|
390
|
+
### Routing Behavior
|
|
391
|
+
|
|
392
|
+
- `parent: null` → Top-level routes: `GET /products/`, `GET /products/:product_id`
|
|
393
|
+
- `parent: "posts"` → Nested routes: `GET /posts/:post_id/comments/`, `GET /posts/:post_id/comments/:comment_id`
|
|
394
|
+
- Child routes are **also** mounted at top-level for direct access: `GET /comments/:comment_id`
|
|
395
|
+
|
|
396
|
+
### Best Practice: Don't Nest System Tables
|
|
397
|
+
|
|
398
|
+
Tables like `users`, `tenants`, `roles`, `permissions`, `sessions`, `accounts`, and `auth_tokens` are cross-cutting concerns. They are referenced by almost every feature module via foreign key columns (`user_id`, `tenant_id`), but they should **not** be used as `parent` values. Only use `parent` for true domain hierarchies:
|
|
399
|
+
|
|
400
|
+
- `posts → comments`
|
|
401
|
+
- `orders → order_items`
|
|
402
|
+
- `projects → tasks`
|
|
403
|
+
- `invoices → invoice_items`
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## How Column Rules Affect Code Generation
|
|
408
|
+
|
|
409
|
+
### Migration Generation
|
|
410
|
+
|
|
411
|
+
The sub-type controls the SQL column type in generated migrations:
|
|
412
|
+
|
|
413
|
+
```sql
|
|
414
|
+
-- "name": "required|string:varchar(300)|minLength:3|maxLength:300"
|
|
415
|
+
CREATE TABLE products (
|
|
416
|
+
name VARCHAR(300) NOT NULL,
|
|
417
|
+
...
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
-- "description": "string:text|maxLength:5000"
|
|
421
|
+
CREATE TABLE products (
|
|
422
|
+
description TEXT,
|
|
423
|
+
...
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
-- "stock_quantity": "required|integer:unsigned|min:0"
|
|
427
|
+
CREATE TABLE products (
|
|
428
|
+
stock_quantity INT UNSIGNED NOT NULL, -- MySQL
|
|
429
|
+
stock_quantity INTEGER NOT NULL, -- PostgreSQL (no unsigned)
|
|
430
|
+
...
|
|
431
|
+
);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Model Generation
|
|
435
|
+
|
|
436
|
+
Validation rules are extracted and passed to the model structure for runtime enforcement:
|
|
437
|
+
|
|
438
|
+
```js
|
|
439
|
+
// Generated model structure
|
|
440
|
+
const products = model(
|
|
441
|
+
db,
|
|
442
|
+
"products",
|
|
443
|
+
{
|
|
444
|
+
name: "required|string|minLength:3|maxLength:300",
|
|
445
|
+
slug: "required|string|regex:^[a-z0-9-]+$|maxLength:300",
|
|
446
|
+
price: "required|numeric|min:0",
|
|
447
|
+
weight_unit: "string|in:kg,lb,oz,g",
|
|
448
|
+
// ...
|
|
449
|
+
},
|
|
450
|
+
"product_id",
|
|
451
|
+
["sku", "slug"],
|
|
452
|
+
{ safeDelete: "is_deleted" },
|
|
453
|
+
);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
### OpenAPI Generation
|
|
459
|
+
|
|
460
|
+
Validators map to OpenAPI schema properties:
|
|
461
|
+
|
|
462
|
+
| Validator | OpenAPI Property |
|
|
463
|
+
| --------------- | ------------------------------------- |
|
|
464
|
+
| `email` | `format: "email"` |
|
|
465
|
+
| `url` | `format: "uri"` |
|
|
466
|
+
| `ip` | `format: "ipv4"` |
|
|
467
|
+
| `phoneNumber` | `pattern: "^\\+?[0-9\\s\\-\\(\\)]+$"` |
|
|
468
|
+
| `minLength:N` | `minLength: N` |
|
|
469
|
+
| `maxLength:N` | `maxLength: N` |
|
|
470
|
+
| `min:N` | `minimum: N` |
|
|
471
|
+
| `max:N` | `maximum: N` |
|
|
472
|
+
| `between:N1,N2` | `minimum: N1, maximum: N2` |
|
|
473
|
+
| `in:a,b,c` | `enum: ["a", "b", "c"]` |
|
|
474
|
+
| `regex:PATTERN` | `pattern: "PATTERN"` |
|
|
475
|
+
| `alpha` | `pattern: "^[a-zA-Z]+$"` |
|
|
476
|
+
| `alphaNumeric` | `pattern: "^[a-zA-Z0-9]+$"` |
|
|
477
|
+
| `alphaDash` | `pattern: "^[a-zA-Z0-9_-]+$"` |
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Complete Example Schema
|
|
482
|
+
|
|
483
|
+
```json
|
|
484
|
+
{
|
|
485
|
+
"adapter": "postgres",
|
|
486
|
+
"framework": "express",
|
|
487
|
+
"options": {
|
|
488
|
+
"session": "redis",
|
|
489
|
+
"rateLimiting": true,
|
|
490
|
+
"helmet": true,
|
|
491
|
+
"logger": true,
|
|
492
|
+
"loki": false
|
|
493
|
+
},
|
|
494
|
+
"tables": {
|
|
495
|
+
"products": {
|
|
496
|
+
"columns": {
|
|
497
|
+
"product_id": "auto_increment",
|
|
498
|
+
"name": "required|string|minLength:3|maxLength:300",
|
|
499
|
+
"slug": "required|string|regex:^[a-z0-9-]+$|maxLength:300",
|
|
500
|
+
"description": "string:text|maxLength:5000",
|
|
501
|
+
"price": "required|numeric:decimal(10,2)|min:0",
|
|
502
|
+
"currency": "required|string|minLength:3|maxLength:3",
|
|
503
|
+
"is_active": "boolean",
|
|
504
|
+
"config": "object",
|
|
505
|
+
"config.primary": "required|string",
|
|
506
|
+
"config.secondary": "required|string",
|
|
507
|
+
"config.border_color": "string|regex:^[#a-fA-F0-9]+$",
|
|
508
|
+
"is_deleted": "boolean",
|
|
509
|
+
"created_at": "datetime",
|
|
510
|
+
"modified_at": "datetime"
|
|
511
|
+
},
|
|
512
|
+
"pk": "product_id",
|
|
513
|
+
"unique": ["slug"],
|
|
514
|
+
"softDelete": "is_deleted",
|
|
515
|
+
"timestamps": {
|
|
516
|
+
"created_at": "created_at",
|
|
517
|
+
"modified_at": "modified_at"
|
|
518
|
+
},
|
|
519
|
+
"parent": null
|
|
520
|
+
},
|
|
521
|
+
"orders": {
|
|
522
|
+
"columns": {
|
|
523
|
+
"order_id": "auto_increment",
|
|
524
|
+
"user_id": "required|integer:unsigned",
|
|
525
|
+
"order_number": "required|string|alphaDash|maxLength:50",
|
|
526
|
+
"status": "required|string|in:pending,processing,shipped,delivered,cancelled",
|
|
527
|
+
"total": "required|numeric:money|min:0",
|
|
528
|
+
"currency": "required|string|minLength:3|maxLength:3",
|
|
529
|
+
"notes": "string:text|maxLength:2000",
|
|
530
|
+
"is_deleted": "boolean",
|
|
531
|
+
"created_at": "datetime",
|
|
532
|
+
"modified_at": "datetime"
|
|
533
|
+
},
|
|
534
|
+
"pk": "order_id",
|
|
535
|
+
"unique": ["order_number"],
|
|
536
|
+
"softDelete": "is_deleted",
|
|
537
|
+
"timestamps": {
|
|
538
|
+
"created_at": "created_at",
|
|
539
|
+
"modified_at": "modified_at"
|
|
540
|
+
},
|
|
541
|
+
"parent": null
|
|
542
|
+
},
|
|
543
|
+
"order_items": {
|
|
544
|
+
"columns": {
|
|
545
|
+
"order_item_id": "auto_increment",
|
|
546
|
+
"order_id": "required|integer:unsigned",
|
|
547
|
+
"product_id": "required|integer:unsigned",
|
|
548
|
+
"quantity": "required|integer:unsigned|min:1",
|
|
549
|
+
"unit_price": "required|numeric:money|min:0",
|
|
550
|
+
"discount_amount": "numeric:money|min:0",
|
|
551
|
+
"created_at": "datetime",
|
|
552
|
+
"modified_at": "datetime"
|
|
553
|
+
},
|
|
554
|
+
"pk": "order_item_id",
|
|
555
|
+
"unique": ["order_item_id"],
|
|
556
|
+
"timestamps": {
|
|
557
|
+
"created_at": "created_at",
|
|
558
|
+
"modified_at": "modified_at"
|
|
559
|
+
},
|
|
560
|
+
"parent": "orders"
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
"relationships": [
|
|
564
|
+
{
|
|
565
|
+
"parent": "orders",
|
|
566
|
+
"child": "order_items",
|
|
567
|
+
"foreignKey": "order_id"
|
|
568
|
+
}
|
|
569
|
+
]
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Backward Compatibility
|
|
576
|
+
|
|
577
|
+
The column rule syntax is fully backward compatible:
|
|
578
|
+
|
|
579
|
+
- `"required|string"` — still works (VARCHAR(255) NOT NULL, no extra validation)
|
|
580
|
+
- `"integer"` — still works (INTEGER, nullable, no extra validation)
|
|
581
|
+
- `"string:text"` — new sub-type, no validation
|
|
582
|
+
- `"required|string|email"` — new validation, default sub-type
|
|
583
|
+
- `"required|string:text|minLength:10|maxLength:5000"` — full syntax
|
|
584
|
+
|
|
585
|
+
Existing schemas without sub-types or validators continue to work unchanged.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Best Practices
|
|
590
|
+
|
|
591
|
+
1. **Include every column** in `columns`, even auto-managed ones (PK, timestamps, soft-delete). The generator excludes them from the model structure automatically.
|
|
592
|
+
2. **Use the `<table>_id` convention** for primary keys: `user_id`, `order_id`, `product_id`.
|
|
593
|
+
3. **Never define SaaS tables** (`users`, `tenants`, `roles`, `role_permissions`) in your schema. They are generated automatically.
|
|
594
|
+
4. **Use `parent` only for domain hierarchies**, not for system tables.
|
|
595
|
+
5. **Use `object` columns for flexible nested data**, and add dot-notation sub-keys for API-level validation without bloating the database schema.
|
|
596
|
+
6. **Set `softDelete` and `timestamps`** whenever you need audit trails; the runtime handles them transparently.
|
|
597
|
+
7. **Use `unique`** wisely — it drives the `upsert` conflict resolution. If a table has a natural unique key (like `sku` or `email`), include it.
|