@vibeorm/dsl 0.3.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.
- package/README.md +62 -471
- package/dist/column.d.ts +150 -0
- package/dist/column.d.ts.map +1 -0
- package/dist/enum.d.ts +27 -0
- package/dist/enum.d.ts.map +1 -0
- package/dist/generator/index.d.ts +5 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/prisma.d.ts +19 -0
- package/dist/generator/prisma.d.ts.map +1 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1546 -0
- package/dist/index.js.map +22 -0
- package/dist/model.d.ts +44 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/naming.d.ts +23 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/relation-plan.d.ts +101 -0
- package/dist/relation-plan.d.ts.map +1 -0
- package/dist/relation.d.ts +23 -0
- package/dist/relation.d.ts.map +1 -0
- package/dist/relations.d.ts +29 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/schema.d.ts +38 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/templates.d.ts +79 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/to-ir.d.ts +41 -0
- package/dist/to-ir.d.ts.map +1 -0
- package/dist/types.d.ts +286 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validate.d.ts +22 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +29 -26
- package/src/column.ts +0 -389
- package/src/enum.ts +0 -71
- package/src/generator/index.ts +0 -5
- package/src/generator/prisma.ts +0 -728
- package/src/index.ts +0 -247
- package/src/model.ts +0 -330
- package/src/relation.ts +0 -75
- package/src/relations.ts +0 -293
- package/src/schema.ts +0 -184
- package/src/templates.ts +0 -134
- package/src/types.ts +0 -381
- package/src/validate.ts +0 -141
package/README.md
CHANGED
|
@@ -1,523 +1,114 @@
|
|
|
1
1
|
# @vibeorm/dsl
|
|
2
2
|
|
|
3
|
-
TypeScript
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
+
## Usage
|
|
121
16
|
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
const UserGroup = dsl.create({
|
|
191
|
-
name: "UserGroup",
|
|
35
|
+
const Post = dsl.create({
|
|
36
|
+
name: "Post",
|
|
192
37
|
schema: {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
44
|
+
indexes: (i) => [i.unique("authorId", "title")],
|
|
198
45
|
});
|
|
199
|
-
```
|
|
200
46
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
+
On every column: `.optional()`, `.unique()`, `.index()`, `.array()`, `.default(value)`, `.map("db_col")`, `.dbgenerated("expr")`, `.comment("...")`.
|
|
362
71
|
|
|
363
|
-
|
|
72
|
+
Kind-specific: `.maxLength(n)` and `.text()` on strings, `.precision(p, s)` on decimals, `.defaultNow()`, `.updatedAt()` and `.tz()` on datetimes.
|
|
364
73
|
|
|
365
|
-
|
|
74
|
+
## Relations
|
|
366
75
|
|
|
367
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
398
|
-
const
|
|
399
|
-
name: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
## Links
|
|
517
109
|
|
|
518
|
-
-
|
|
519
|
-
-
|
|
520
|
-
-
|
|
521
|
-
-
|
|
522
|
-
-
|
|
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
|