peta-orm 0.3.0 → 0.4.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 +186 -233
- package/dist/{collection-Dv3sQPMx.mjs → collection-PFmrQHyM.mjs} +1 -1
- package/dist/{factory-rIbPGjRg.mjs → factory-BBvIMQuc.mjs} +1 -1
- package/dist/index.d.mts +478 -2
- package/dist/index.mjs +12 -9
- package/dist/{save-B8rudcT5.mjs → save-D5UKXvqC.mjs} +1 -1
- package/package.json +5 -12
- package/bin/peta +0 -3
- package/dist/index-BdJnSMYi.d.mts +0 -480
- package/dist/migrations/cli.d.mts +0 -4
- package/dist/migrations/cli.mjs +0 -74
- package/dist/migrations/index.d.mts +0 -53
- package/dist/migrations/index.mjs +0 -2
- package/dist/runner-DQ7uT6LC.mjs +0 -180
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# peta-orm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/peta-orm)
|
|
4
|
+
[](https://www.typescriptlang.org)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
A feature-rich ORM for Bun, built on [Kysely](https://kysely.dev) with [ArkType](https://arktype.io) validation. ActiveRecord-style models, typed relations, lazy/eager loading, lifecycle hooks, soft deletes, timestamps, casting, serialization control, global scopes, polymorphic relations, pagination, collections, and more — all fully typed end-to-end.
|
|
6
8
|
|
|
7
9
|
```ts
|
|
8
10
|
const user = await User.insert({ name: "Alice", email: "a@b.com" })
|
|
9
|
-
const posts = await user
|
|
10
|
-
const page = await Post.query().with("author").paginate(1, 20)
|
|
11
|
+
const posts = await User.relations.posts.query(user).where("published", true).execute()
|
|
12
|
+
const page = await Post.query().with("author").orderBy("id", "asc").paginate(1, 20)
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
---
|
|
@@ -15,75 +17,49 @@ const page = await Post.query().with("author").paginate(1, 20)
|
|
|
15
17
|
## Quick Start
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
bun add peta-orm arktype kysely
|
|
19
|
-
bun add -d kysely-bun-sqlite
|
|
20
|
+
bun add peta-orm arktype kysely @libsql/kysely-libsql @libsql/client
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
```ts
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import type { ColumnShape } from "peta-orm"
|
|
27
|
-
import { Peta, $t, ArkTypeSchemaConfig, Model, HasMany } from "peta-orm"
|
|
24
|
+
import { createClient } from "@libsql/client"
|
|
25
|
+
import { LibsqlDialect } from "@libsql/kysely-libsql"
|
|
26
|
+
import { createORM, defineModel, t } from "peta-orm"
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
static override table = "users"
|
|
33
|
-
static override columns = {
|
|
34
|
-
id: t.integer().primaryKey(),
|
|
35
|
-
name: t.string(255).min(2),
|
|
36
|
-
email: t.text().email().unique(),
|
|
37
|
-
} satisfies ColumnShape
|
|
38
|
-
static override relations = {
|
|
39
|
-
posts: new HasMany(() => Post),
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class Post extends Model {
|
|
44
|
-
static override table = "posts"
|
|
45
|
-
static override columns = {
|
|
46
|
-
id: t.integer().primaryKey(),
|
|
47
|
-
userId: t.integer().references(() => User, ["id"]),
|
|
48
|
-
title: t.string(255),
|
|
49
|
-
} satisfies ColumnShape
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const database = new Database("my-app.db")
|
|
53
|
-
database.run(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL)`)
|
|
54
|
-
database.run(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, userId INTEGER NOT NULL, title TEXT NOT NULL)`)
|
|
55
|
-
|
|
56
|
-
const peta = new Peta({ dialect: new BunSqliteDialect({ database }) })
|
|
28
|
+
const orm = createORM({
|
|
29
|
+
dialect: new LibsqlDialect({ url: "file:my-app.db" }),
|
|
30
|
+
})
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
|
|
32
|
+
const User = defineModel("users", {
|
|
33
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255), email: t.text().unique() },
|
|
34
|
+
})
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
// await peta.discover("./src/**/*.model.ts")
|
|
36
|
+
orm.registerAll(User)
|
|
63
37
|
|
|
64
|
-
|
|
38
|
+
const user = await User.insert({ name: "Alice", email: "alice@test.com" })
|
|
65
39
|
```
|
|
66
40
|
|
|
41
|
+
> [!TIP]
|
|
42
|
+
> See the 32 [runnable examples](./examples) for every feature. Run them with `bun run examples/XX-*.ts`.
|
|
43
|
+
|
|
67
44
|
---
|
|
68
45
|
|
|
69
|
-
## Why
|
|
46
|
+
## Why peta-orm?
|
|
70
47
|
|
|
71
|
-
| Feature | Raw Kysely |
|
|
48
|
+
| Feature | Raw Kysely | peta-orm |
|
|
72
49
|
|---------|-----------|----------|
|
|
73
50
|
| **Validation** | Manual | Automatic from column definitions via ArkType |
|
|
74
51
|
| **Models** | Row types only | Class instances with `$save()`, `$delete()`, `$reload()` |
|
|
75
|
-
| **Relations** | Manual JOINs | Declarative `
|
|
52
|
+
| **Relations** | Manual JOINs | Declarative `hasMany`, `belongsTo`, `hasOne`, `manyToMany` |
|
|
76
53
|
| **Eager loading** | Manual batch | `.with("posts.author")` — one line, batched queries |
|
|
77
54
|
| **Hooks** | — | `beforeCreate`, `afterUpdate`, `beforeDelete`, etc. |
|
|
78
55
|
| **Soft deletes** | — | `withTrashed()`, `onlyTrashed()`, `$restore()`, `$forceDelete()` |
|
|
79
56
|
| **Casting** | — | `$casts: { meta: "json", flags: "boolean" }` |
|
|
80
57
|
| **Serialization** | — | `$hidden`, `$visible`, `$appends`, accessors |
|
|
81
58
|
| **Pagination** | Manual offset/limit | `.paginate(1, 20)` — returns `{ data, total, perPage, ... }` |
|
|
82
|
-
| **
|
|
83
|
-
| **Error handling** | Raw driver codes | `DatabaseError` with `UNIQUE_CONSTRAINT` / `FOREIGN_KEY_CONSTRAINT` |
|
|
59
|
+
| **Error handling** | Raw driver codes | `DatabaseError` with `UNIQUE_CONSTRAINT` across dialects |
|
|
84
60
|
| **Conditional queries** | Manual if/else | `.when(condition, qb => ...)`, `.unless(condition, qb => ...)` |
|
|
85
|
-
| **Migrations** | — | Auto-generate from models, CLI, `MigrationRunner` |
|
|
86
61
|
| **Global scopes** | — | `addGlobalScope("active", qb => ...)` |
|
|
62
|
+
| **Polymorphic relations** | — | `morphTo`, `morphMany`, `morphOne` |
|
|
87
63
|
|
|
88
64
|
---
|
|
89
65
|
|
|
@@ -91,91 +67,76 @@ export { peta, User, Post }
|
|
|
91
67
|
|
|
92
68
|
### Column Types & Validation
|
|
93
69
|
|
|
94
|
-
|
|
95
|
-
import type { ColumnShape } from "peta-orm"
|
|
70
|
+
Column definitions double as validation schemas — no separate validation step needed.
|
|
96
71
|
|
|
97
|
-
|
|
72
|
+
```ts
|
|
73
|
+
const t = columnTypes({ schema: createArkTypeSchemaConfig() })
|
|
98
74
|
|
|
99
|
-
|
|
100
|
-
|
|
75
|
+
const User = defineModel("users", {
|
|
76
|
+
columns: {
|
|
101
77
|
id: t.integer().primaryKey(),
|
|
102
|
-
name: t.string(255).min(2),
|
|
103
|
-
email: t.text().email().unique(),
|
|
78
|
+
name: t.string(255).min(2), // min length
|
|
79
|
+
email: t.text().email().unique(), // email format + unique
|
|
104
80
|
age: t.integer().nullable().min(0).max(150).default(0),
|
|
105
81
|
role: t.enum("admin", "user").default("user"),
|
|
106
82
|
score: t.double().nullable(),
|
|
107
|
-
...t.timestamps(),
|
|
108
|
-
}
|
|
109
|
-
}
|
|
83
|
+
...t.timestamps(), // createdAt, updatedAt
|
|
84
|
+
},
|
|
85
|
+
})
|
|
110
86
|
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
const Post = defineModel("posts", {
|
|
88
|
+
columns: {
|
|
113
89
|
id: t.integer().primaryKey(),
|
|
114
|
-
userId: t.integer()
|
|
90
|
+
userId: t.integer(),
|
|
115
91
|
title: t.string(255),
|
|
116
92
|
slug: t.string().unique(),
|
|
117
93
|
published: t.boolean().default(false),
|
|
118
|
-
}
|
|
119
|
-
}
|
|
94
|
+
},
|
|
95
|
+
})
|
|
120
96
|
```
|
|
121
97
|
|
|
122
98
|
### Relations & Eager Loading
|
|
123
99
|
|
|
124
100
|
```ts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
101
|
+
const User = defineModel("users", {
|
|
102
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255) },
|
|
103
|
+
relations: {
|
|
104
|
+
posts: hasMany(() => Post, { foreignKey: "userId" }),
|
|
105
|
+
profile: hasOne(() => Profile, { foreignKey: "userId" }),
|
|
106
|
+
},
|
|
107
|
+
})
|
|
131
108
|
|
|
132
109
|
// Eager load with dot notation
|
|
133
|
-
const users = await User.query()
|
|
134
|
-
.with("posts")
|
|
135
|
-
.with("posts.author")
|
|
136
|
-
.with({ posts: (q) => q.where("published", true) })
|
|
137
|
-
.execute()
|
|
110
|
+
const users = await User.query().with("posts.author").execute()
|
|
138
111
|
|
|
139
112
|
// Lazy load after fetch
|
|
140
113
|
await user.$load("posts")
|
|
141
|
-
await collection.load("posts.author")
|
|
142
114
|
|
|
143
115
|
// Relation query
|
|
144
|
-
const posts = await user
|
|
116
|
+
const posts = await User.relations.posts.query(user).where("published", true).execute()
|
|
145
117
|
|
|
146
118
|
// Existence filters
|
|
147
119
|
const authors = await User.query().has("posts").execute()
|
|
148
120
|
const active = await User.query().whereHas("posts", (q) => q.where("published", true)).execute()
|
|
149
121
|
```
|
|
150
122
|
|
|
151
|
-
###
|
|
123
|
+
### Polymorphic Relations
|
|
152
124
|
|
|
153
125
|
```ts
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
class Tag extends Model {
|
|
166
|
-
static override columns = { id: t.integer().primaryKey(), name: t.string(255) } satisfies ColumnShape
|
|
167
|
-
}
|
|
126
|
+
const Comment = defineModel("comments", {
|
|
127
|
+
columns: { id: t.integer().primaryKey(), body: t.text() },
|
|
128
|
+
relations: {
|
|
129
|
+
subject: morphTo(() => ({
|
|
130
|
+
Post: { foreignKey: "postId" },
|
|
131
|
+
Article: { foreignKey: "articleId" },
|
|
132
|
+
})),
|
|
133
|
+
},
|
|
134
|
+
})
|
|
168
135
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
static override columns = {
|
|
174
|
-
id: t.integer().primaryKey(),
|
|
175
|
-
postId: t.integer().references(() => Post, ["id"]),
|
|
176
|
-
tagId: t.integer().references(() => Tag, ["id"]),
|
|
177
|
-
} satisfies ColumnShape
|
|
178
|
-
}
|
|
136
|
+
const Post = defineModel("posts", {
|
|
137
|
+
columns: { id: t.integer().primaryKey(), title: t.string(255) },
|
|
138
|
+
relations: { comments: morphMany(() => Comment, { morphType: "post" }) },
|
|
139
|
+
})
|
|
179
140
|
```
|
|
180
141
|
|
|
181
142
|
### CRUD & Pagination
|
|
@@ -199,137 +160,133 @@ await User.delete(1)
|
|
|
199
160
|
|
|
200
161
|
// Paginate
|
|
201
162
|
const page = await Post.query().orderBy("id", "asc").paginate(1, 20)
|
|
202
|
-
// → { data: Post[], total, perPage, currentPage, lastPage, hasMorePages }
|
|
203
|
-
|
|
204
|
-
// Query results are plain T[] — standard, zero overhead
|
|
205
|
-
const posts = await Post.query().where("published", true).execute()
|
|
206
|
-
// posts: Post[]
|
|
207
|
-
posts[0] // direct index access
|
|
163
|
+
// → { data: Post[], total: 30, perPage: 20, currentPage: 1, lastPage: 2, hasMorePages: true }
|
|
208
164
|
```
|
|
209
165
|
|
|
210
166
|
### Hooks & Timestamps
|
|
211
167
|
|
|
212
168
|
```ts
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.on("beforeCreate", (user) => { user.email = user.email.toLowerCase() })
|
|
216
|
-
this.on("afterCreate", (user) => { console.log("Created:", user.get("id")) })
|
|
217
|
-
}
|
|
218
|
-
}
|
|
169
|
+
User.on("beforeCreate", (user) => { user.email = user.email.toLowerCase() })
|
|
170
|
+
User.on("afterCreate", (user) => { console.log("Created:", user.get("id")) })
|
|
219
171
|
|
|
220
|
-
|
|
172
|
+
// Timestamps plugin sets createdAt/updatedAt automatically
|
|
173
|
+
const Timestamped = defineModel("ts", {
|
|
174
|
+
columns: { ...t.timestamps(), ...t.integer().primaryKey(), name: t.string(255) },
|
|
175
|
+
}).use(timestamps())
|
|
221
176
|
```
|
|
222
177
|
|
|
223
178
|
### Soft Deletes
|
|
224
179
|
|
|
225
180
|
```ts
|
|
226
|
-
|
|
181
|
+
const SoftModel = defineModel("items", {
|
|
182
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255), ...t.timestamps() },
|
|
183
|
+
}).use(softDeletes())
|
|
227
184
|
|
|
228
|
-
await
|
|
229
|
-
await
|
|
230
|
-
await
|
|
185
|
+
await item.$delete() // sets deletedAt
|
|
186
|
+
await item.$restore() // clears deletedAt
|
|
187
|
+
await item.$forceDelete() // actually deletes
|
|
231
188
|
|
|
232
|
-
const active = await
|
|
233
|
-
const all = await
|
|
234
|
-
const trashed = await
|
|
189
|
+
const active = await SoftModel.query().execute() // excludes deleted
|
|
190
|
+
const all = await SoftModel.query().withTrashed().execute() // includes deleted
|
|
191
|
+
const trashed = await SoftModel.query().onlyTrashed().execute() // only deleted
|
|
235
192
|
```
|
|
236
193
|
|
|
237
|
-
###
|
|
194
|
+
### Graph Operations
|
|
238
195
|
|
|
239
|
-
|
|
240
|
-
class User extends Model {
|
|
241
|
-
static override $casts = {
|
|
242
|
-
meta: "json",
|
|
243
|
-
flags: "boolean",
|
|
244
|
-
createdAt: "date",
|
|
245
|
-
}
|
|
246
|
-
static override $hidden = ["password"]
|
|
247
|
-
static override $visible = ["id", "name", "email"] // whitelist
|
|
248
|
-
static override $appends = ["fullName"]
|
|
249
|
-
|
|
250
|
-
getFullNameAttribute() { return `${this.get("first")} ${this.get("last")}` }
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const json = user.$toJSON() // password excluded, fullName appended, meta parsed
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Global Scopes & Transactions
|
|
196
|
+
Insert or upsert nested models in a single call:
|
|
257
197
|
|
|
258
198
|
```ts
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
199
|
+
const user = await User.insertGraph({
|
|
200
|
+
name: "Alice",
|
|
201
|
+
posts: [{ title: "Post 1" }, { title: "Post 2" }],
|
|
202
|
+
})
|
|
263
203
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
204
|
+
const updated = await User.upsertGraph({
|
|
205
|
+
id: user.get("id"),
|
|
206
|
+
name: "Alice Updated",
|
|
207
|
+
posts: [{ id: 1, title: "Post 1 Updated" }, { title: "New Post" }],
|
|
268
208
|
})
|
|
269
209
|
```
|
|
270
210
|
|
|
271
|
-
###
|
|
211
|
+
### Casting & Serialization
|
|
272
212
|
|
|
273
213
|
```ts
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.unless(sort?.length, (q) => q.orderBy("createdAt", "desc"))
|
|
283
|
-
.execute()
|
|
284
|
-
```
|
|
214
|
+
const User = defineModel("users", {
|
|
215
|
+
columns: {
|
|
216
|
+
id: t.integer().primaryKey(), name: t.string(255),
|
|
217
|
+
meta: t.json(), flags: t.boolean(), password: t.string(255),
|
|
218
|
+
},
|
|
219
|
+
$casts: { meta: "json", flags: "boolean" },
|
|
220
|
+
$hidden: ["password"],
|
|
221
|
+
})
|
|
285
222
|
|
|
286
|
-
|
|
223
|
+
const json = user.$toJSON() // password excluded, meta parsed from JSON
|
|
224
|
+
```
|
|
287
225
|
|
|
288
226
|
### Error Handling
|
|
289
227
|
|
|
290
|
-
Database constraint violations (unique, foreign key) are normalized into a `DatabaseError` across SQLite, PostgreSQL, and MySQL:
|
|
291
|
-
|
|
292
228
|
```ts
|
|
293
|
-
import { DatabaseError } from "peta-orm"
|
|
294
|
-
|
|
295
229
|
try {
|
|
296
|
-
|
|
230
|
+
await Post.insert({ slug: "my-post" })
|
|
297
231
|
} catch (e) {
|
|
298
232
|
if (e instanceof DatabaseError && e.code === "UNIQUE_CONSTRAINT") {
|
|
299
|
-
|
|
300
|
-
return c.json({ error: "Slug already taken" }, 400)
|
|
233
|
+
return c.json({ error: "Slug taken" }, 400)
|
|
301
234
|
}
|
|
302
235
|
throw e
|
|
303
236
|
}
|
|
304
237
|
```
|
|
305
238
|
|
|
306
|
-
|
|
|
307
|
-
|
|
308
|
-
| `UNIQUE_CONSTRAINT` | Duplicate value
|
|
309
|
-
| `FOREIGN_KEY_CONSTRAINT` |
|
|
310
|
-
|
|
311
|
-
The error also carries the `table` name and the original driver error via `cause`.
|
|
239
|
+
| Code | Meaning | Driver errors |
|
|
240
|
+
|------|---------|--------------|
|
|
241
|
+
| `UNIQUE_CONSTRAINT` | Duplicate value | `SQLITE_CONSTRAINT_UNIQUE`, PG `23505`, MySQL `ER_DUP_ENTRY` |
|
|
242
|
+
| `FOREIGN_KEY_CONSTRAINT` | Missing referenced row | `SQLITE_CONSTRAINT_FOREIGNKEY`, PG `23503`, MySQL `ER_NO_REFERENCED_ROW_2` |
|
|
312
243
|
|
|
313
|
-
###
|
|
244
|
+
### Collections
|
|
314
245
|
|
|
315
246
|
```ts
|
|
316
|
-
// .execute() returns a plain array — lightweight, direct index access
|
|
317
|
-
const users = await User.query().execute()
|
|
318
|
-
users[0] // direct access
|
|
319
|
-
|
|
320
|
-
// .collect() returns a Collection with convenience methods
|
|
321
247
|
const col = await User.query().orderBy("id", "asc").collect()
|
|
322
|
-
col.toJSON() // all items serialized in one call
|
|
323
248
|
col.pluck("name") // ["Alice", "Bob"]
|
|
324
249
|
col.groupBy("role") // { admin: [...], user: [...] }
|
|
325
250
|
col.load("posts") // eager load relations
|
|
326
|
-
col.sum("score")
|
|
327
|
-
col.avg("age")
|
|
328
|
-
col.unique("role")
|
|
329
|
-
col.sortBy("name")
|
|
251
|
+
col.sum("score")
|
|
330
252
|
col.chunk(10) // split into batches
|
|
331
|
-
|
|
332
|
-
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Global Scopes & Conditional Chaining
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
User.addGlobalScope("active", (qb) => qb.where("active", "=", 1))
|
|
259
|
+
await User.query().withoutGlobalScope("active").execute()
|
|
260
|
+
|
|
261
|
+
const posts = await Post.query()
|
|
262
|
+
.when(sort?.length, (q) => q.orderBy(sort[0]!, "asc"))
|
|
263
|
+
.unless(sort?.length, (q) => q.orderBy("createdAt", "desc"))
|
|
264
|
+
.execute()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Migrations
|
|
270
|
+
|
|
271
|
+
Generate and run migrations from model definitions:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { createMigrationRunner, createMigrationGenerator } from "peta-orm/migrator"
|
|
275
|
+
|
|
276
|
+
const runner = createMigrationRunner(kysely)
|
|
277
|
+
const gen = createMigrationGenerator()
|
|
278
|
+
|
|
279
|
+
const code = gen.generateInitialMigration(models)
|
|
280
|
+
await runner.up(migrationFiles)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Or via the CLI:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
bun run bin/peta migrate:init
|
|
287
|
+
bun run bin/peta migrate:generate
|
|
288
|
+
bun run bin/peta migrate:up
|
|
289
|
+
bun run bin/peta migrate:status
|
|
333
290
|
```
|
|
334
291
|
|
|
335
292
|
---
|
|
@@ -342,69 +299,65 @@ All self-contained (inline SQLite, run directly):
|
|
|
342
299
|
bun run examples/01-basic-setup.ts
|
|
343
300
|
bun run examples/04-relations.ts
|
|
344
301
|
bun run examples/07-soft-deletes.ts
|
|
345
|
-
|
|
346
|
-
# CLI — manage migrations
|
|
347
|
-
bun run bin/peta --help
|
|
348
|
-
bun run bin/peta migrate:init
|
|
349
|
-
bun run bin/peta migrate:generate CreateUsers
|
|
350
|
-
bun run bin/peta migrate:up
|
|
351
|
-
bun run bin/peta migrate:status
|
|
352
302
|
```
|
|
353
303
|
|
|
354
304
|
| # | Example | Topic |
|
|
355
305
|
|---|---------|-------|
|
|
356
|
-
| 01 | [basic-setup](./examples/01-basic-setup.ts) |
|
|
306
|
+
| 01 | [basic-setup](./examples/01-basic-setup.ts) | ORM init + SQLite setup |
|
|
357
307
|
| 02 | [model-definition](./examples/02-model-definition.ts) | Columns, types, modifiers, timestamps |
|
|
358
308
|
| 03 | [crud](./examples/03-crud.ts) | insert, find, update, delete, paginate |
|
|
359
|
-
| 04 | [relations](./examples/04-relations.ts) |
|
|
360
|
-
| 05 | [query-builder](./examples/05-query-builder.ts) | where, orderBy, join, has, whereHas
|
|
361
|
-
| 06 | [hooks-timestamps](./examples/06-hooks-timestamps.ts) | beforeCreate, afterCreate,
|
|
309
|
+
| 04 | [relations](./examples/04-relations.ts) | hasMany, belongsTo, hasOne, eager loading |
|
|
310
|
+
| 05 | [query-builder](./examples/05-query-builder.ts) | where, orderBy, join, has, whereHas |
|
|
311
|
+
| 06 | [hooks-timestamps](./examples/06-hooks-timestamps.ts) | beforeCreate, afterCreate, timestamps |
|
|
362
312
|
| 07 | [soft-deletes](./examples/07-soft-deletes.ts) | $delete, $restore, $forceDelete, withTrashed |
|
|
363
313
|
| 08 | [collection-paginator](./examples/08-collection-paginator.ts) | Collection, Paginator, `.collect()` |
|
|
364
|
-
| 09 | [hono-integration](./examples/09-hono-integration.ts) | Hono app +
|
|
314
|
+
| 09 | [hono-integration](./examples/09-hono-integration.ts) | Hono app + DatabaseError handling |
|
|
365
315
|
| 10 | [elysia-integration](./examples/10-elysia-integration.ts) | Elysia app stub |
|
|
366
316
|
| 11 | [many-to-many](./examples/11-many-to-many.ts) | ManyToMany via pivot table |
|
|
367
317
|
| 12 | [transactions](./examples/12-transactions.ts) | Model.transaction(), rollback |
|
|
368
318
|
| 13 | [casting](./examples/13-casting.ts) | $casts, $hidden, $appends, accessors |
|
|
369
319
|
| 14 | [global-scopes](./examples/14-global-scopes.ts) | addGlobalScope(), withoutGlobalScope() |
|
|
370
|
-
| 15 | [batch](./examples/15-batch.ts) | insertMany
|
|
320
|
+
| 15 | [batch](./examples/15-batch.ts) | insertMany |
|
|
371
321
|
| 16 | [discover](./examples/16-discover.ts) | peta.discover(), rest params |
|
|
372
|
-
| 17 | [instance-methods](./examples/17-instance-methods.ts) | fill, dirty, reset, $reload, $load
|
|
373
|
-
| 18 | [advanced-queries](./examples/18-advanced-queries.ts) | groupBy/having,
|
|
374
|
-
| 19 | [collections-deep](./examples/19-collections-deep.ts) |
|
|
375
|
-
| 20 | [advanced-relations](./examples/20-advanced-relations.ts) | HasManyThrough, polymorphic morphs
|
|
376
|
-
| 21 | [migrations](./examples/21-migrations.ts) | MigrationRunner, MigrationGenerator
|
|
377
|
-
| 22 | [related-query-builder](./examples/22-related-query-builder.ts) | `$related()` — scoped
|
|
378
|
-
| 23 | [attach-detach-sync](./examples/23-attach-detach-sync.ts) | Many-to-many pivot management
|
|
322
|
+
| 17 | [instance-methods](./examples/17-instance-methods.ts) | fill, dirty, reset, $reload, $load |
|
|
323
|
+
| 18 | [advanced-queries](./examples/18-advanced-queries.ts) | groupBy/having, aggregate helpers, chunk |
|
|
324
|
+
| 19 | [collections-deep](./examples/19-collections-deep.ts) | Full Collection + Paginator API |
|
|
325
|
+
| 20 | [advanced-relations](./examples/20-advanced-relations.ts) | HasManyThrough, polymorphic morphs |
|
|
326
|
+
| 21 | [migrations](./examples/21-migrations.ts) | MigrationRunner, MigrationGenerator |
|
|
327
|
+
| 22 | [related-query-builder](./examples/22-related-query-builder.ts) | `$related()` — scoped relation queries |
|
|
328
|
+
| 23 | [attach-detach-sync](./examples/23-attach-detach-sync.ts) | Many-to-many pivot management |
|
|
379
329
|
| 24 | [computed-columns](./examples/24-computed-columns.ts) | Runtime + batch async computed columns |
|
|
380
|
-
| 25 | [static-hooks](./examples/25-static-hooks.ts) | `asFindQuery()`
|
|
381
|
-
| 26 | [repository-pattern](./examples/26-repository-pattern.ts) | `createRepo()` —
|
|
382
|
-
| 27 | [plugins-and-helpers](./examples/27-plugins-and-helpers.ts) | `.use()` plugin system +
|
|
383
|
-
| 28 | [nested-create-update](./examples/28-nested-create-update.ts) | Create
|
|
384
|
-
| 29 | [allow-graph](./examples/29-allow-graph.ts) | `allowGraph()` — recursive
|
|
385
|
-
| 30 | [polymorphic-relations](./examples/30-polymorphic-relations.ts) |
|
|
330
|
+
| 25 | [static-hooks](./examples/25-static-hooks.ts) | `asFindQuery()` + `cancelQuery()` |
|
|
331
|
+
| 26 | [repository-pattern](./examples/26-repository-pattern.ts) | `createRepo()` — custom query methods |
|
|
332
|
+
| 27 | [plugins-and-helpers](./examples/27-plugins-and-helpers.ts) | `.use()` plugin system + makeHelper() |
|
|
333
|
+
| 28 | [nested-create-update](./examples/28-nested-create-update.ts) | Create/update with related data in one call |
|
|
334
|
+
| 29 | [allow-graph](./examples/29-allow-graph.ts) | `allowGraph()` — recursive eager load whitelist |
|
|
335
|
+
| 30 | [polymorphic-relations](./examples/30-polymorphic-relations.ts) | MorphMany/MorphOne/MorphTo |
|
|
386
336
|
| 31 | [graph-operations](./examples/31-graph-operations.ts) | `insertGraph()`/`upsertGraph()` with `#id`/`#ref` |
|
|
387
|
-
| 32 | [accessors-mutators](./examples/32-accessors-mutators.ts) | `Attribute.make({ get, set })`
|
|
337
|
+
| 32 | [accessors-mutators](./examples/32-accessors-mutators.ts) | `Attribute.make({ get, set })` |
|
|
388
338
|
|
|
389
339
|
---
|
|
390
340
|
|
|
391
|
-
##
|
|
392
|
-
|
|
393
|
-
|
|
|
394
|
-
|
|
395
|
-
|
|
|
396
|
-
|
|
|
397
|
-
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
341
|
+
## Database Support
|
|
342
|
+
|
|
343
|
+
| Database | Dialect package | Status |
|
|
344
|
+
|----------|----------------|--------|
|
|
345
|
+
| SQLite | `@libsql/kysely-libsql` + `@libsql/client` | ✅ Tested |
|
|
346
|
+
| PostgreSQL | `pg` | ✅ Tested via Docker |
|
|
347
|
+
| MySQL | `mysql2` | ✅ Tested via Docker |
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
docker compose up -d # PostgreSQL 16 + MySQL 8.0
|
|
351
|
+
cd packages/orm
|
|
352
|
+
bun test test/integration/
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Set `INTEGRATION_SKIP_PG=1` or `INTEGRATION_SKIP_MYSQL=1` to skip specific databases.
|
|
405
356
|
|
|
406
357
|
---
|
|
407
358
|
|
|
408
|
-
##
|
|
359
|
+
## Related packages
|
|
409
360
|
|
|
410
|
-
|
|
361
|
+
- [peta-auth](../auth) — Encrypted cookie sessions, JWT, OAuth
|
|
362
|
+
- [peta-docs](../docs) — OpenAPI 3.1 spec generation + Scalar UI
|
|
363
|
+
- [peta-migrate](../migrate) — Standalone migration runner and generator
|
|
@@ -153,7 +153,7 @@ function createCollection(items) {
|
|
|
153
153
|
async load(...relations) {
|
|
154
154
|
if (data.length === 0) return collection;
|
|
155
155
|
const { EagerLoader } = await import("./index.mjs").then((n) => n.i);
|
|
156
|
-
const { getModelDefFromInstance } = await import("./factory-
|
|
156
|
+
const { getModelDefFromInstance } = await import("./factory-BBvIMQuc.mjs").then((n) => n.n);
|
|
157
157
|
const { getModelDef } = await import("./index.mjs").then((n) => n.n);
|
|
158
158
|
const first = data[0];
|
|
159
159
|
const def = getModelDefFromInstance(first) ?? getModelDef(first);
|