@vibeorm/dsl 0.4.0 → 2.0.0-alpha.1

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/README.md +62 -471
  2. package/dist/column.d.ts +150 -0
  3. package/dist/column.d.ts.map +1 -0
  4. package/dist/enum.d.ts +27 -0
  5. package/dist/enum.d.ts.map +1 -0
  6. package/dist/generator/index.d.ts +5 -0
  7. package/dist/generator/index.d.ts.map +1 -0
  8. package/dist/generator/prisma.d.ts +19 -0
  9. package/dist/generator/prisma.d.ts.map +1 -0
  10. package/dist/index.d.ts +116 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +1546 -0
  13. package/dist/index.js.map +22 -0
  14. package/dist/model.d.ts +44 -0
  15. package/dist/model.d.ts.map +1 -0
  16. package/dist/naming.d.ts +23 -0
  17. package/dist/naming.d.ts.map +1 -0
  18. package/dist/relation-plan.d.ts +101 -0
  19. package/dist/relation-plan.d.ts.map +1 -0
  20. package/dist/relation.d.ts +23 -0
  21. package/dist/relation.d.ts.map +1 -0
  22. package/dist/relations.d.ts +29 -0
  23. package/dist/relations.d.ts.map +1 -0
  24. package/dist/schema.d.ts +38 -0
  25. package/dist/schema.d.ts.map +1 -0
  26. package/dist/templates.d.ts +79 -0
  27. package/dist/templates.d.ts.map +1 -0
  28. package/dist/to-ir.d.ts +41 -0
  29. package/dist/to-ir.d.ts.map +1 -0
  30. package/dist/types.d.ts +286 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/validate.d.ts +22 -0
  33. package/dist/validate.d.ts.map +1 -0
  34. package/package.json +29 -26
  35. package/src/column.ts +0 -389
  36. package/src/enum.ts +0 -71
  37. package/src/generator/index.ts +0 -5
  38. package/src/generator/prisma.ts +0 -728
  39. package/src/index.ts +0 -247
  40. package/src/model.ts +0 -330
  41. package/src/relation.ts +0 -75
  42. package/src/relations.ts +0 -293
  43. package/src/schema.ts +0 -184
  44. package/src/templates.ts +0 -134
  45. package/src/types.ts +0 -381
  46. package/src/validate.ts +0 -141
package/README.md CHANGED
@@ -1,523 +1,114 @@
1
1
  # @vibeorm/dsl
2
2
 
3
- TypeScript DSL for defining Prisma schemas programmatically. Define models, columns, relations, and enums in TypeScript and generate valid `.prisma` schema files.
3
+ > Part of **[VibeORM](https://github.com/vibeorm/vibeorm)** — a type-safe TypeScript ORM for Bun and Node. Prisma-schema or TypeScript-DSL input, a canonical schema IR, a generated client, and a dialect-aware SQL layer over PostgreSQL, PGlite, SQLite and MySQL.
4
4
 
5
- ## Install
5
+ The code-first schema input: models and relations written in TypeScript, compiled straight to the canonical `SchemaIR`. It is the peer of the `.prisma` parser, and it also emits `.prisma` text for interop.
6
6
 
7
7
  ```bash
8
- bun add @vibeorm/dsl
8
+ bun add @vibeorm/dsl@alpha
9
9
  ```
10
10
 
11
- ## Quick Start
11
+ > **Pre-release.** `2.0.0-alpha.x`, published under the `alpha` dist-tag. `npm i vibeorm` still resolves to the stable v1 line.
12
12
 
13
- ```typescript
14
- import { dsl } from "@vibeorm/dsl";
15
-
16
- // Define models
17
- const Workspace = dsl.create({
18
- name: "Workspace",
19
- schema: {
20
- ...dsl.templates.base,
21
- name: dsl.string(),
22
- slug: dsl.string().unique(),
23
- },
24
- });
25
-
26
- const User = dsl.create({
27
- name: "User",
28
- schema: {
29
- ...dsl.templates.base,
30
- email: dsl.string().unique(),
31
- workspaceId: dsl.string().index(),
32
- },
33
- });
34
-
35
- // Define relations
36
- const relations = dsl.defineRelations({ Workspace, User }, (r) => ({
37
- Workspace: {
38
- users: r.many.User({
39
- from: r.Workspace.id,
40
- to: r.User.workspaceId,
41
- }),
42
- },
43
- User: {
44
- workspace: r.one.Workspace({
45
- from: r.User.workspaceId,
46
- to: r.Workspace.id,
47
- }),
48
- },
49
- }));
50
-
51
- // Assemble schema and generate
52
- const schema = dsl.schema({ Workspace, User }, { relations });
53
- const prismaOutput = dsl.generate.prisma(schema);
54
- ```
55
-
56
- ## Column Types
57
-
58
- Every column builder supports both **object syntax** and **chainable syntax**:
59
-
60
- ```typescript
61
- // Object syntax
62
- dsl.string({ optional: true, unique: true, default: "hello" })
63
-
64
- // Chainable syntax (equivalent)
65
- dsl.string().optional().unique().default("hello")
66
- ```
67
-
68
- ### Available Types
69
-
70
- | Builder | Prisma Type | Notes |
71
- |---|---|---|
72
- | `dsl.string()` | `String` | `.maxLength(n)` for `@db.VarChar(n)`, `.text()` for `@db.Text` |
73
- | `dsl.int()` | `Int` | |
74
- | `dsl.bigint()` | `BigInt` | |
75
- | `dsl.float()` | `Float` | |
76
- | `dsl.decimal()` | `Decimal` | `.precision(p, s)` for `@db.Decimal(p, s)` |
77
- | `dsl.boolean()` | `Boolean` | |
78
- | `dsl.datetime()` | `DateTime` | `.defaultNow()`, `.updatedAt()`, `.tz()` for `@db.Timestamptz()` |
79
- | `dsl.json()` | `Json` | |
80
- | `dsl.id()` | `String`/`Int` | `.type("ulid" \| "cuid" \| "uuid" \| "autoincrement")` |
81
- | `dsl.bytes()` | `Bytes` | |
82
- | `dsl.unsupported("tsvector")` | `Unsupported("tsvector")` | For database-native types without Prisma mapping |
83
-
84
- ### Common Modifiers
85
-
86
- All column types support these chainable methods:
87
-
88
- ```typescript
89
- dsl.string()
90
- .optional() // Nullable (String?)
91
- .unique() // @unique
92
- .index() // @@index
93
- .array() // String[]
94
- .default("value") // @default("value")
95
- .map("db_column_name") // @map("db_column_name")
96
- .dbgenerated("expression") // @default(dbgenerated("expression"))
97
- .comment("text") // /// text (triple-slash comment above column)
98
- ```
99
-
100
- ### ID Types
101
-
102
- ```typescript
103
- dsl.id() // String @id @default(ulid()) — default
104
- dsl.id().type("cuid") // String @id @default(cuid())
105
- dsl.id().type("uuid") // String @id @default(uuid())
106
- dsl.id().type("autoincrement") // Int @id @default(autoincrement())
107
- ```
108
-
109
- ### Datetime Shortcuts
110
-
111
- ```typescript
112
- dsl.datetime().defaultNow() // @default(now())
113
- dsl.datetime().updatedAt() // @updatedAt
114
- dsl.datetime().tz() // @db.Timestamptz()
115
- dsl.datetime().defaultNow().updatedAt().tz() // All combined
116
- ```
117
-
118
- ## Enums
13
+ Most users never install this directly — it ships as a dependency of [`vibeorm`](https://www.npmjs.com/package/vibeorm). Reach for it when you are building tooling on the IR or SQL layer.
119
14
 
120
- ### Standalone Enums (Reusable)
15
+ ## Usage
121
16
 
122
- ```typescript
123
- const UserRole = dsl.enum({
124
- name: "UserRole",
125
- options: ["admin", "user", "guest"],
126
- });
127
-
128
- const User = dsl.create({
129
- name: "User",
130
- schema: {
131
- id: dsl.id(),
132
- role: UserRole.field().default("user"),
133
- },
134
- });
135
- ```
136
-
137
- ### Inline Enums (One-Off)
138
-
139
- Auto-named as `ModelNameColumnName`:
140
-
141
- ```typescript
142
- const Project = dsl.create({
143
- name: "Project",
144
- schema: {
145
- id: dsl.id(),
146
- // Generates: enum ProjectStatus { active, archived, deleted }
147
- status: dsl.enum({ options: ["active", "archived", "deleted"] }).default("active"),
148
- },
149
- });
150
- ```
151
-
152
- ## Models
17
+ ```ts
18
+ // src/schema.ts
19
+ import { dsl } from "@vibeorm/dsl";
153
20
 
154
- ### Basic Model
21
+ const Role = dsl.enum({ name: "Role", options: ["USER", "ADMIN"] });
155
22
 
156
- ```typescript
157
23
  const User = dsl.create({
158
24
  name: "User",
159
25
  schema: {
160
- id: dsl.id(),
26
+ id: dsl.id({ type: "autoincrement" }),
27
+ createdAt: dsl.datetime().defaultNow(),
28
+ updatedAt: dsl.datetime().updatedAt(),
161
29
  email: dsl.string().unique(),
162
- name: dsl.string(),
163
- bio: dsl.string().optional(),
30
+ name: dsl.string().optional(),
31
+ role: Role.field({ default: "USER" }),
164
32
  },
165
33
  });
166
- ```
167
-
168
- ### With Indexes
169
-
170
- The `indexes` callback provides a type-safe index builder:
171
-
172
- ```typescript
173
- const User = dsl.create({
174
- name: "User",
175
- schema: {
176
- id: dsl.id(),
177
- email: dsl.string().index(), // Single-column: @@index([email])
178
- organizationId: dsl.string().index(),
179
- },
180
- indexes: (i) => [
181
- i.unique("organizationId", "email"), // @@unique([organizationId, email])
182
- i.gin("tags"), // @@index([tags], type: Gin)
183
- ],
184
- });
185
- ```
186
34
 
187
- ### With Compound Primary Key
188
-
189
- ```typescript
190
- const UserGroup = dsl.create({
191
- name: "UserGroup",
35
+ const Post = dsl.create({
36
+ name: "Post",
192
37
  schema: {
193
- userId: dsl.string(),
194
- groupId: dsl.string(),
195
- joinedAt: dsl.datetime().defaultNow(),
38
+ id: dsl.id({ type: "uuid" }),
39
+ title: dsl.string(),
40
+ published: dsl.boolean().default(false),
41
+ meta: dsl.json().optional(),
42
+ authorId: dsl.int().index(),
196
43
  },
197
- primaryKey: ["userId", "groupId"], // @@id([userId, groupId])
44
+ indexes: (i) => [i.unique("authorId", "title")],
198
45
  });
199
- ```
200
46
 
201
- ### With Table Mapping
202
-
203
- ```typescript
204
- const User = dsl.create({
205
- name: "User",
206
- schema: {
207
- id: dsl.id(),
208
- firstName: dsl.string().map("first_name"),
209
- },
210
- map: "users", // @@map("users")
211
- });
212
- ```
213
-
214
- ### With Children (Inline Nested Models)
215
-
216
- Use a callback for `schema` to access the children builder:
217
-
218
- ```typescript
219
- const Order = dsl.create({
220
- name: "Order",
221
- schema: (s) => ({
222
- id: dsl.id(),
223
- total: dsl.decimal(),
224
- // Creates an OrderItem model with an auto-generated orderId FK
225
- items: s.children({
226
- name: "OrderItem",
227
- schema: {
228
- id: dsl.id(),
229
- quantity: dsl.int(),
230
- price: dsl.decimal(),
231
- },
232
- }),
233
- }),
234
- });
235
- ```
236
-
237
- ## Relations
238
-
239
- Relations are defined separately from models using `dsl.defineRelations()`. This keeps models focused on columns and allows relations to reference each other.
240
-
241
- ### One-to-Many
242
-
243
- ```typescript
244
- const relations = dsl.defineRelations({ Workspace, User }, (r) => ({
245
- Workspace: {
246
- users: r.many.User({
247
- from: r.Workspace.id,
248
- to: r.User.workspaceId,
249
- }),
250
- },
251
- User: {
252
- workspace: r.one.Workspace({
253
- from: r.User.workspaceId,
254
- to: r.Workspace.id,
255
- }),
256
- },
47
+ const relations = dsl.defineRelations({ User, Post }, (r) => ({
48
+ User: { posts: r.many.Post({ from: r.User.id, to: r.Post.authorId }) },
49
+ Post: { author: r.one.User({ from: r.Post.authorId, to: r.User.id, onDelete: "Cascade" }) },
257
50
  }));
258
- ```
259
-
260
- ### One-to-One
261
51
 
262
- When both sides define `r.one`, the generator merges them into a single Prisma relation. The side with the FK column becomes the owning side:
263
-
264
- ```typescript
265
- const relations = dsl.defineRelations({ User, UserProfile }, (r) => ({
266
- User: {
267
- profile: r.one.UserProfile({
268
- from: r.User.id,
269
- to: r.UserProfile.userId,
270
- }),
271
- },
272
- UserProfile: {
273
- user: r.one.User({
274
- from: r.UserProfile.userId,
275
- to: r.User.id,
276
- onDelete: "Cascade",
277
- }),
278
- },
279
- }));
52
+ export const schema = dsl.schema({ Role, User, Post }, { relations });
53
+ export default schema;
280
54
  ```
281
55
 
282
- Generates:
56
+ Point `vibeorm.config.ts` at the `.ts` entry (`schema: "./src/schema.ts"`) and the CLI takes it from there. The module must export the composed schema as `default`, as `export const schema`, or as a `schema` property on the default export.
283
57
 
284
- ```prisma
285
- model User {
286
- // ...
287
- Profile UserProfile?
288
- }
289
-
290
- model UserProfile {
291
- // ...
292
- User User @relation(fields: [userId], references: [id], onDelete: Cascade)
293
- }
294
- ```
58
+ To go to the IR yourself: `dsl.toIR({ schema })` returns a `SchemaIR`, optionally taking `provider` and an `extensions` registry. `dsl.validateSchema(schema)` returns `ValidationError[]` without throwing.
295
59
 
296
- ### Self-Referential Relations
297
-
298
- Use `alias` to disambiguate when a model references itself:
299
-
300
- ```typescript
301
- Task: {
302
- parentTask: r.one.Task({
303
- from: r.Task.parentTaskId,
304
- to: r.Task.id,
305
- optional: true,
306
- alias: "TaskHierarchy",
307
- }),
308
- childTasks: r.many.Task({
309
- from: r.Task.id,
310
- to: r.Task.parentTaskId,
311
- alias: "TaskHierarchy",
312
- }),
313
- },
314
- ```
60
+ ## Column factories
315
61
 
316
- ### Multiple Relations to Same Model
317
-
318
- Use `alias` to distinguish different relations between the same models:
319
-
320
- ```typescript
321
- Referral: {
322
- referrer: r.one.User({
323
- from: r.Referral.referrerId,
324
- to: r.User.id,
325
- alias: "ReferralReferrer",
326
- }),
327
- referredUser: r.one.User({
328
- from: r.Referral.referredUserId,
329
- to: r.User.id,
330
- optional: true,
331
- alias: "ReferralReferred",
332
- }),
333
- },
334
- ```
62
+ `dsl.string()` · `dsl.int()` · `dsl.bigint()` · `dsl.float()` · `dsl.decimal()` · `dsl.boolean()` · `dsl.datetime()` · `dsl.json()` · `dsl.bytes()` · `dsl.id({ type })` · `dsl.unsupported("tsvector")` · `dsl.extensionType({ ... })` · `dsl.enum(...)`.
335
63
 
336
- ### Optional Relations
64
+ `dsl.enum` has two forms: with a `name` it creates a reusable enum whose `.field()` yields a column, without one it creates an inline single-use enum column.
337
65
 
338
- ```typescript
339
- User: {
340
- workspace: r.one.Workspace({
341
- from: r.User.workspaceId,
342
- to: r.Workspace.id,
343
- optional: true, // Workspace?
344
- }),
345
- },
346
- ```
66
+ Id strategies are `dsl.id({ type: "ulid" | "uuid" | "cuid" | "nanoid" | "autoincrement" | "none" })`. The default is `"ulid"`, a `String` column generated in the app; `"autoincrement"` makes it an `Int`.
347
67
 
348
- ### Cascade Actions
349
-
350
- ```typescript
351
- UserProfile: {
352
- user: r.one.User({
353
- from: r.UserProfile.userId,
354
- to: r.User.id,
355
- onDelete: "Cascade", // onDelete: Cascade
356
- onUpdate: "Cascade", // onUpdate: Cascade
357
- }),
358
- },
359
- ```
68
+ ## Chainable modifiers
360
69
 
361
- Supported actions: `"Cascade"`, `"SetNull"`, `"Restrict"`, `"NoAction"`, `"SetDefault"`.
70
+ On every column: `.optional()`, `.unique()`, `.index()`, `.array()`, `.default(value)`, `.map("db_col")`, `.dbgenerated("expr")`, `.comment("...")`.
362
71
 
363
- ### Splitting Relations Across Files
72
+ Kind-specific: `.maxLength(n)` and `.text()` on strings, `.precision(p, s)` on decimals, `.defaultNow()`, `.updatedAt()` and `.tz()` on datetimes.
364
73
 
365
- For large schemas, split relation definitions with `defineRelationsPart` and `mergeRelations`:
74
+ ## Relations
366
75
 
367
- ```typescript
368
- // file: user-relations.ts
369
- export const userRelations = dsl.defineRelationsPart({ User, Workspace }, (r) => ({
370
- User: {
371
- workspace: r.one.Workspace({
372
- from: r.User.workspaceId,
373
- to: r.Workspace.id,
374
- }),
375
- },
376
- }));
76
+ Relations are declared separately from columns, in `dsl.defineRelations(models, (r) => ...)`, which keeps column references typed on both ends.
377
77
 
378
- // file: workspace-relations.ts
379
- export const workspaceRelations = dsl.defineRelationsPart({ User, Workspace }, (r) => ({
380
- Workspace: {
381
- users: r.many.User({
382
- from: r.Workspace.id,
383
- to: r.User.workspaceId,
384
- }),
385
- },
386
- }));
78
+ - One side: `r.one.X({ from, to, optional?, alias?, onDelete?, onUpdate? })`.
79
+ - Many side: `r.many.X({ from, to })`.
80
+ - Implicit many-to-many: both sides `r.many`.
81
+ - Explicit many-to-many, through a junction model: `r.many.Group({ from: r.User.id.through(r.UserGroup.userId), to: r.Group.id.through(r.UserGroup.groupId) })`.
82
+ - Self-relations, and several relations between the same pair of models, disambiguate with `alias`.
387
83
 
388
- // file: schema.ts
389
- const relations = dsl.mergeRelations(userRelations, workspaceRelations);
390
- const schema = dsl.schema({ User, Workspace }, { relations });
391
- ```
84
+ Large schemas can split relations across files with `dsl.defineRelationsPart` and recombine them with `dsl.mergeRelations`; `dsl.domain({ name, models })` groups models.
392
85
 
393
86
  ## Templates
394
87
 
395
- Built-in field templates for common patterns. Templates use getters, so every spread produces fresh column instances (no shared mutable state):
88
+ `dsl.templates.base` spreads `id`, `createdAt`, `updatedAt` and `deletedAt` into a model. Also available: `baseWithWorkspace`, `withAudit`, `withActive`, `withMetadata`, `withNameDescription`, `withExternalId`, and `fields`.
396
89
 
397
- ```typescript
398
- const User = dsl.create({
399
- name: "User",
400
- schema: {
401
- ...dsl.templates.base, // id, createdAt, updatedAt, deletedAt
402
- ...dsl.templates.withMetadata, // metadata, settings (optional Json)
403
- email: dsl.string().unique(),
404
- },
90
+ ```ts
91
+ const Workspace = dsl.create({
92
+ name: "Workspace",
93
+ schema: { ...dsl.templates.base, name: dsl.string() },
405
94
  });
406
95
  ```
407
96
 
408
- ### Available Templates
409
-
410
- | Template | Fields |
411
- |---|---|
412
- | `dsl.templates.base` | `id` (ULID), `createdAt`, `updatedAt`, `deletedAt` (optional) |
413
- | `dsl.templates.baseWithWorkspace` | Same as `base` + `workspaceId` (indexed String) |
414
- | `dsl.templates.withMetadata` | `metadata`, `settings` (optional Json) |
415
- | `dsl.templates.withActive` | `isActive` (Boolean, default `true`) |
416
- | `dsl.templates.withAudit` | `createdById`, `updatedById` (optional, indexed) |
417
- | `dsl.templates.withExternalId` | `externalId` (indexed String) |
418
- | `dsl.templates.withNameDescription` | `name` (String), `description` (optional String) |
419
-
420
- Individual fields are also available via `dsl.templates.fields`:
421
-
422
- ```typescript
423
- dsl.templates.fields.id // ULID primary key
424
- dsl.templates.fields.datetime // DateTime with now + updatedAt + tz
425
- dsl.templates.fields.title // String
426
- dsl.templates.fields.description // Optional String
427
- dsl.templates.fields.metadata // Optional Json
428
- dsl.templates.fields.settings // Optional Json
429
- dsl.templates.fields.isActive // Boolean default true
430
- dsl.templates.fields.externalId // Indexed String
431
- ```
432
-
433
- ## Schema Assembly
97
+ ## Interop
434
98
 
435
- ### Basic Schema
436
-
437
- ```typescript
438
- const schema = dsl.schema({ User, Workspace }, { relations });
439
- ```
440
-
441
- ### With Standalone Enums
442
-
443
- Pass standalone enums alongside models:
444
-
445
- ```typescript
446
- const UserRole = dsl.enum({ name: "UserRole", options: ["admin", "user"] });
447
-
448
- const schema = dsl.schema(
449
- { UserRole, User, Workspace },
450
- { relations }
451
- );
452
- ```
453
-
454
- ### Grouped by Domain
455
-
456
- ```typescript
457
- const schema = dsl.schema({
458
- UserRole,
459
- User,
460
- shipping: { Carrier, CarrierService },
461
- printing: { PrintAccount, PrintJob },
462
- }, { relations });
463
- ```
99
+ `dsl.generate.prisma(schema)` renders the schema as `.prisma` text, and `dsl.generate(schema)` returns it as a `GeneratorOutput`. That is what backs the CLI's conversion in both directions:
464
100
 
465
- ## Generating Prisma Output
466
-
467
- ```typescript
468
- const schema = dsl.schema({ User, Workspace }, { relations });
469
-
470
- // Generate just the models + enums (no datasource/generator blocks)
471
- const prismaBody = dsl.generate.prisma(schema);
472
- ```
473
-
474
- The output contains only model and enum definitions. Add your own datasource and generator blocks:
475
-
476
- ```typescript
477
- import { writeFileSync } from "fs";
478
-
479
- const fullSchema = `datasource db {
480
- provider = "postgresql"
481
- url = env("DATABASE_URL")
482
- }
483
-
484
- generator client {
485
- provider = "prisma-client"
486
- output = "../src/generated/prisma"
487
- }
488
-
489
- ${prismaBody}
490
- `;
491
-
492
- writeFileSync("prisma/schema.prisma", fullSchema);
493
- ```
494
-
495
- ## Validation
496
-
497
- Check for common issues before generating:
498
-
499
- ```typescript
500
- const errors = dsl.validateSchema(schema);
501
-
502
- for (const error of errors) {
503
- console.log(`[${error.type}] ${error.model}: ${error.message}`);
504
- }
101
+ ```bash
102
+ vibeorm convert --to dsl --out src/schema.ts # .prisma → TS DSL
103
+ vibeorm convert --to prisma --out prisma/schema.prisma # TS DSL → .prisma
505
104
  ```
506
105
 
507
- Checks for:
508
- - Duplicate model/enum names
509
- - Missing FK columns referenced in relations
510
- - Self-referential relations without aliases
511
- - Models without an `id` column or compound `primaryKey`
512
- - Relations targeting non-existent models
513
-
514
- ## Full Example
106
+ Conversion goes through the IR and round-trips are tested: emitted DSL compiles back to the same IR. `convert --to dsl` refuses constructs the DSL cannot express — views, composite-column foreign keys, non-standard index kinds, enum `@@map` names, exotic `@db.*` types — with one `VIBE_CONFIG` error naming every offender.
515
107
 
516
- See `tests/fixtures/advanced-schema.ts` for a comprehensive 11-model schema covering:
108
+ ## Links
517
109
 
518
- - Standalone and inline enums
519
- - One-to-many, one-to-one, self-referential, and multi-FK relations
520
- - Many-to-many via junction table
521
- - Children (nested inline models)
522
- - Array columns, optional FKs, composite indexes
523
- - Relation aliases for disambiguation
110
+ - [Documentation](https://github.com/vibeorm/vibeorm/tree/master/docs)
111
+ - [Getting started](https://github.com/vibeorm/vibeorm/blob/master/docs/getting-started.md)
112
+ - [CLI reference](https://github.com/vibeorm/vibeorm/blob/master/docs/cli.md)
113
+ - [Repository and issues](https://github.com/vibeorm/vibeorm)
114
+ - MIT licensed