peta-orm 0.3.0 → 0.4.0
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
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
|
---
|
|
@@ -20,70 +22,45 @@ bun add -d kysely-bun-sqlite
|
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
```ts
|
|
23
|
-
// db.ts
|
|
24
25
|
import { Database } from "bun:sqlite"
|
|
25
26
|
import { BunSqliteDialect } from "kysely-bun-sqlite"
|
|
26
|
-
import
|
|
27
|
-
import { Peta, $t, ArkTypeSchemaConfig, Model, HasMany } from "peta-orm"
|
|
27
|
+
import { createORM, defineModel, t } from "peta-orm"
|
|
28
28
|
|
|
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 }) })
|
|
29
|
+
const orm = createORM({
|
|
30
|
+
dialect: new BunSqliteDialect({ database: new Database("my-app.db") }),
|
|
31
|
+
})
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
const User = defineModel("users", {
|
|
34
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255), email: t.text().unique() },
|
|
35
|
+
})
|
|
60
36
|
|
|
61
|
-
|
|
62
|
-
// await peta.discover("./src/**/*.model.ts")
|
|
37
|
+
orm.registerAll(User)
|
|
63
38
|
|
|
64
|
-
|
|
39
|
+
const user = await User.insert({ name: "Alice", email: "alice@test.com" })
|
|
65
40
|
```
|
|
66
41
|
|
|
42
|
+
> [!TIP]
|
|
43
|
+
> See the 32 [runnable examples](./examples) for every feature. Run them with `bun run examples/XX-*.ts`.
|
|
44
|
+
|
|
67
45
|
---
|
|
68
46
|
|
|
69
|
-
## Why
|
|
47
|
+
## Why peta-orm?
|
|
70
48
|
|
|
71
|
-
| Feature | Raw Kysely |
|
|
49
|
+
| Feature | Raw Kysely | peta-orm |
|
|
72
50
|
|---------|-----------|----------|
|
|
73
51
|
| **Validation** | Manual | Automatic from column definitions via ArkType |
|
|
74
52
|
| **Models** | Row types only | Class instances with `$save()`, `$delete()`, `$reload()` |
|
|
75
|
-
| **Relations** | Manual JOINs | Declarative `
|
|
53
|
+
| **Relations** | Manual JOINs | Declarative `hasMany`, `belongsTo`, `hasOne`, `manyToMany` |
|
|
76
54
|
| **Eager loading** | Manual batch | `.with("posts.author")` — one line, batched queries |
|
|
77
55
|
| **Hooks** | — | `beforeCreate`, `afterUpdate`, `beforeDelete`, etc. |
|
|
78
56
|
| **Soft deletes** | — | `withTrashed()`, `onlyTrashed()`, `$restore()`, `$forceDelete()` |
|
|
79
57
|
| **Casting** | — | `$casts: { meta: "json", flags: "boolean" }` |
|
|
80
58
|
| **Serialization** | — | `$hidden`, `$visible`, `$appends`, accessors |
|
|
81
59
|
| **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` |
|
|
60
|
+
| **Error handling** | Raw driver codes | `DatabaseError` with `UNIQUE_CONSTRAINT` across dialects |
|
|
84
61
|
| **Conditional queries** | Manual if/else | `.when(condition, qb => ...)`, `.unless(condition, qb => ...)` |
|
|
85
|
-
| **Migrations** | — | Auto-generate from models, CLI, `MigrationRunner` |
|
|
86
62
|
| **Global scopes** | — | `addGlobalScope("active", qb => ...)` |
|
|
63
|
+
| **Polymorphic relations** | — | `morphTo`, `morphMany`, `morphOne` |
|
|
87
64
|
|
|
88
65
|
---
|
|
89
66
|
|
|
@@ -91,91 +68,76 @@ export { peta, User, Post }
|
|
|
91
68
|
|
|
92
69
|
### Column Types & Validation
|
|
93
70
|
|
|
94
|
-
|
|
95
|
-
import type { ColumnShape } from "peta-orm"
|
|
71
|
+
Column definitions double as validation schemas — no separate validation step needed.
|
|
96
72
|
|
|
97
|
-
|
|
73
|
+
```ts
|
|
74
|
+
const t = columnTypes({ schema: createArkTypeSchemaConfig() })
|
|
98
75
|
|
|
99
|
-
|
|
100
|
-
|
|
76
|
+
const User = defineModel("users", {
|
|
77
|
+
columns: {
|
|
101
78
|
id: t.integer().primaryKey(),
|
|
102
|
-
name: t.string(255).min(2),
|
|
103
|
-
email: t.text().email().unique(),
|
|
79
|
+
name: t.string(255).min(2), // min length
|
|
80
|
+
email: t.text().email().unique(), // email format + unique
|
|
104
81
|
age: t.integer().nullable().min(0).max(150).default(0),
|
|
105
82
|
role: t.enum("admin", "user").default("user"),
|
|
106
83
|
score: t.double().nullable(),
|
|
107
|
-
...t.timestamps(),
|
|
108
|
-
}
|
|
109
|
-
}
|
|
84
|
+
...t.timestamps(), // createdAt, updatedAt
|
|
85
|
+
},
|
|
86
|
+
})
|
|
110
87
|
|
|
111
|
-
|
|
112
|
-
|
|
88
|
+
const Post = defineModel("posts", {
|
|
89
|
+
columns: {
|
|
113
90
|
id: t.integer().primaryKey(),
|
|
114
|
-
userId: t.integer()
|
|
91
|
+
userId: t.integer(),
|
|
115
92
|
title: t.string(255),
|
|
116
93
|
slug: t.string().unique(),
|
|
117
94
|
published: t.boolean().default(false),
|
|
118
|
-
}
|
|
119
|
-
}
|
|
95
|
+
},
|
|
96
|
+
})
|
|
120
97
|
```
|
|
121
98
|
|
|
122
99
|
### Relations & Eager Loading
|
|
123
100
|
|
|
124
101
|
```ts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
102
|
+
const User = defineModel("users", {
|
|
103
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255) },
|
|
104
|
+
relations: {
|
|
105
|
+
posts: hasMany(() => Post, { foreignKey: "userId" }),
|
|
106
|
+
profile: hasOne(() => Profile, { foreignKey: "userId" }),
|
|
107
|
+
},
|
|
108
|
+
})
|
|
131
109
|
|
|
132
110
|
// 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()
|
|
111
|
+
const users = await User.query().with("posts.author").execute()
|
|
138
112
|
|
|
139
113
|
// Lazy load after fetch
|
|
140
114
|
await user.$load("posts")
|
|
141
|
-
await collection.load("posts.author")
|
|
142
115
|
|
|
143
116
|
// Relation query
|
|
144
|
-
const posts = await user
|
|
117
|
+
const posts = await User.relations.posts.query(user).where("published", true).execute()
|
|
145
118
|
|
|
146
119
|
// Existence filters
|
|
147
120
|
const authors = await User.query().has("posts").execute()
|
|
148
121
|
const active = await User.query().whereHas("posts", (q) => q.where("published", true)).execute()
|
|
149
122
|
```
|
|
150
123
|
|
|
151
|
-
###
|
|
124
|
+
### Polymorphic Relations
|
|
152
125
|
|
|
153
126
|
```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
|
-
}
|
|
127
|
+
const Comment = defineModel("comments", {
|
|
128
|
+
columns: { id: t.integer().primaryKey(), body: t.text() },
|
|
129
|
+
relations: {
|
|
130
|
+
subject: morphTo(() => ({
|
|
131
|
+
Post: { foreignKey: "postId" },
|
|
132
|
+
Article: { foreignKey: "articleId" },
|
|
133
|
+
})),
|
|
134
|
+
},
|
|
135
|
+
})
|
|
168
136
|
|
|
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
|
-
}
|
|
137
|
+
const Post = defineModel("posts", {
|
|
138
|
+
columns: { id: t.integer().primaryKey(), title: t.string(255) },
|
|
139
|
+
relations: { comments: morphMany(() => Comment, { morphType: "post" }) },
|
|
140
|
+
})
|
|
179
141
|
```
|
|
180
142
|
|
|
181
143
|
### CRUD & Pagination
|
|
@@ -199,137 +161,133 @@ await User.delete(1)
|
|
|
199
161
|
|
|
200
162
|
// Paginate
|
|
201
163
|
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
|
|
164
|
+
// → { data: Post[], total: 30, perPage: 20, currentPage: 1, lastPage: 2, hasMorePages: true }
|
|
208
165
|
```
|
|
209
166
|
|
|
210
167
|
### Hooks & Timestamps
|
|
211
168
|
|
|
212
169
|
```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
|
-
}
|
|
170
|
+
User.on("beforeCreate", (user) => { user.email = user.email.toLowerCase() })
|
|
171
|
+
User.on("afterCreate", (user) => { console.log("Created:", user.get("id")) })
|
|
219
172
|
|
|
220
|
-
|
|
173
|
+
// Timestamps plugin sets createdAt/updatedAt automatically
|
|
174
|
+
const Timestamped = defineModel("ts", {
|
|
175
|
+
columns: { ...t.timestamps(), ...t.integer().primaryKey(), name: t.string(255) },
|
|
176
|
+
}).use(timestamps())
|
|
221
177
|
```
|
|
222
178
|
|
|
223
179
|
### Soft Deletes
|
|
224
180
|
|
|
225
181
|
```ts
|
|
226
|
-
|
|
182
|
+
const SoftModel = defineModel("items", {
|
|
183
|
+
columns: { id: t.integer().primaryKey(), name: t.string(255), ...t.timestamps() },
|
|
184
|
+
}).use(softDeletes())
|
|
227
185
|
|
|
228
|
-
await
|
|
229
|
-
await
|
|
230
|
-
await
|
|
186
|
+
await item.$delete() // sets deletedAt
|
|
187
|
+
await item.$restore() // clears deletedAt
|
|
188
|
+
await item.$forceDelete() // actually deletes
|
|
231
189
|
|
|
232
|
-
const active = await
|
|
233
|
-
const all = await
|
|
234
|
-
const trashed = await
|
|
190
|
+
const active = await SoftModel.query().execute() // excludes deleted
|
|
191
|
+
const all = await SoftModel.query().withTrashed().execute() // includes deleted
|
|
192
|
+
const trashed = await SoftModel.query().onlyTrashed().execute() // only deleted
|
|
235
193
|
```
|
|
236
194
|
|
|
237
|
-
###
|
|
195
|
+
### Graph Operations
|
|
238
196
|
|
|
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
|
|
197
|
+
Insert or upsert nested models in a single call:
|
|
257
198
|
|
|
258
199
|
```ts
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
200
|
+
const user = await User.insertGraph({
|
|
201
|
+
name: "Alice",
|
|
202
|
+
posts: [{ title: "Post 1" }, { title: "Post 2" }],
|
|
203
|
+
})
|
|
263
204
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
205
|
+
const updated = await User.upsertGraph({
|
|
206
|
+
id: user.get("id"),
|
|
207
|
+
name: "Alice Updated",
|
|
208
|
+
posts: [{ id: 1, title: "Post 1 Updated" }, { title: "New Post" }],
|
|
268
209
|
})
|
|
269
210
|
```
|
|
270
211
|
|
|
271
|
-
###
|
|
212
|
+
### Casting & Serialization
|
|
272
213
|
|
|
273
214
|
```ts
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.unless(sort?.length, (q) => q.orderBy("createdAt", "desc"))
|
|
283
|
-
.execute()
|
|
284
|
-
```
|
|
215
|
+
const User = defineModel("users", {
|
|
216
|
+
columns: {
|
|
217
|
+
id: t.integer().primaryKey(), name: t.string(255),
|
|
218
|
+
meta: t.json(), flags: t.boolean(), password: t.string(255),
|
|
219
|
+
},
|
|
220
|
+
$casts: { meta: "json", flags: "boolean" },
|
|
221
|
+
$hidden: ["password"],
|
|
222
|
+
})
|
|
285
223
|
|
|
286
|
-
|
|
224
|
+
const json = user.$toJSON() // password excluded, meta parsed from JSON
|
|
225
|
+
```
|
|
287
226
|
|
|
288
227
|
### Error Handling
|
|
289
228
|
|
|
290
|
-
Database constraint violations (unique, foreign key) are normalized into a `DatabaseError` across SQLite, PostgreSQL, and MySQL:
|
|
291
|
-
|
|
292
229
|
```ts
|
|
293
|
-
import { DatabaseError } from "peta-orm"
|
|
294
|
-
|
|
295
230
|
try {
|
|
296
|
-
|
|
231
|
+
await Post.insert({ slug: "my-post" })
|
|
297
232
|
} catch (e) {
|
|
298
233
|
if (e instanceof DatabaseError && e.code === "UNIQUE_CONSTRAINT") {
|
|
299
|
-
|
|
300
|
-
return c.json({ error: "Slug already taken" }, 400)
|
|
234
|
+
return c.json({ error: "Slug taken" }, 400)
|
|
301
235
|
}
|
|
302
236
|
throw e
|
|
303
237
|
}
|
|
304
238
|
```
|
|
305
239
|
|
|
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`.
|
|
240
|
+
| Code | Meaning | Driver errors |
|
|
241
|
+
|------|---------|--------------|
|
|
242
|
+
| `UNIQUE_CONSTRAINT` | Duplicate value | `SQLITE_CONSTRAINT_UNIQUE`, PG `23505`, MySQL `ER_DUP_ENTRY` |
|
|
243
|
+
| `FOREIGN_KEY_CONSTRAINT` | Missing referenced row | `SQLITE_CONSTRAINT_FOREIGNKEY`, PG `23503`, MySQL `ER_NO_REFERENCED_ROW_2` |
|
|
312
244
|
|
|
313
|
-
###
|
|
245
|
+
### Collections
|
|
314
246
|
|
|
315
247
|
```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
248
|
const col = await User.query().orderBy("id", "asc").collect()
|
|
322
|
-
col.toJSON() // all items serialized in one call
|
|
323
249
|
col.pluck("name") // ["Alice", "Bob"]
|
|
324
250
|
col.groupBy("role") // { admin: [...], user: [...] }
|
|
325
251
|
col.load("posts") // eager load relations
|
|
326
|
-
col.sum("score")
|
|
327
|
-
col.avg("age")
|
|
328
|
-
col.unique("role")
|
|
329
|
-
col.sortBy("name")
|
|
252
|
+
col.sum("score")
|
|
330
253
|
col.chunk(10) // split into batches
|
|
331
|
-
|
|
332
|
-
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Global Scopes & Conditional Chaining
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
User.addGlobalScope("active", (qb) => qb.where("active", "=", 1))
|
|
260
|
+
await User.query().withoutGlobalScope("active").execute()
|
|
261
|
+
|
|
262
|
+
const posts = await Post.query()
|
|
263
|
+
.when(sort?.length, (q) => q.orderBy(sort[0]!, "asc"))
|
|
264
|
+
.unless(sort?.length, (q) => q.orderBy("createdAt", "desc"))
|
|
265
|
+
.execute()
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Migrations
|
|
271
|
+
|
|
272
|
+
Generate and run migrations from model definitions:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { createMigrationRunner, createMigrationGenerator } from "peta-orm/migrator"
|
|
276
|
+
|
|
277
|
+
const runner = createMigrationRunner(kysely)
|
|
278
|
+
const gen = createMigrationGenerator()
|
|
279
|
+
|
|
280
|
+
const code = gen.generateInitialMigration(models)
|
|
281
|
+
await runner.up(migrationFiles)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Or via the CLI:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
bun run bin/peta migrate:init
|
|
288
|
+
bun run bin/peta migrate:generate
|
|
289
|
+
bun run bin/peta migrate:up
|
|
290
|
+
bun run bin/peta migrate:status
|
|
333
291
|
```
|
|
334
292
|
|
|
335
293
|
---
|
|
@@ -342,69 +300,65 @@ All self-contained (inline SQLite, run directly):
|
|
|
342
300
|
bun run examples/01-basic-setup.ts
|
|
343
301
|
bun run examples/04-relations.ts
|
|
344
302
|
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
303
|
```
|
|
353
304
|
|
|
354
305
|
| # | Example | Topic |
|
|
355
306
|
|---|---------|-------|
|
|
356
|
-
| 01 | [basic-setup](./examples/01-basic-setup.ts) |
|
|
307
|
+
| 01 | [basic-setup](./examples/01-basic-setup.ts) | ORM init + SQLite setup |
|
|
357
308
|
| 02 | [model-definition](./examples/02-model-definition.ts) | Columns, types, modifiers, timestamps |
|
|
358
309
|
| 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,
|
|
310
|
+
| 04 | [relations](./examples/04-relations.ts) | hasMany, belongsTo, hasOne, eager loading |
|
|
311
|
+
| 05 | [query-builder](./examples/05-query-builder.ts) | where, orderBy, join, has, whereHas |
|
|
312
|
+
| 06 | [hooks-timestamps](./examples/06-hooks-timestamps.ts) | beforeCreate, afterCreate, timestamps |
|
|
362
313
|
| 07 | [soft-deletes](./examples/07-soft-deletes.ts) | $delete, $restore, $forceDelete, withTrashed |
|
|
363
314
|
| 08 | [collection-paginator](./examples/08-collection-paginator.ts) | Collection, Paginator, `.collect()` |
|
|
364
|
-
| 09 | [hono-integration](./examples/09-hono-integration.ts) | Hono app +
|
|
315
|
+
| 09 | [hono-integration](./examples/09-hono-integration.ts) | Hono app + DatabaseError handling |
|
|
365
316
|
| 10 | [elysia-integration](./examples/10-elysia-integration.ts) | Elysia app stub |
|
|
366
317
|
| 11 | [many-to-many](./examples/11-many-to-many.ts) | ManyToMany via pivot table |
|
|
367
318
|
| 12 | [transactions](./examples/12-transactions.ts) | Model.transaction(), rollback |
|
|
368
319
|
| 13 | [casting](./examples/13-casting.ts) | $casts, $hidden, $appends, accessors |
|
|
369
320
|
| 14 | [global-scopes](./examples/14-global-scopes.ts) | addGlobalScope(), withoutGlobalScope() |
|
|
370
|
-
| 15 | [batch](./examples/15-batch.ts) | insertMany
|
|
321
|
+
| 15 | [batch](./examples/15-batch.ts) | insertMany |
|
|
371
322
|
| 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
|
|
323
|
+
| 17 | [instance-methods](./examples/17-instance-methods.ts) | fill, dirty, reset, $reload, $load |
|
|
324
|
+
| 18 | [advanced-queries](./examples/18-advanced-queries.ts) | groupBy/having, aggregate helpers, chunk |
|
|
325
|
+
| 19 | [collections-deep](./examples/19-collections-deep.ts) | Full Collection + Paginator API |
|
|
326
|
+
| 20 | [advanced-relations](./examples/20-advanced-relations.ts) | HasManyThrough, polymorphic morphs |
|
|
327
|
+
| 21 | [migrations](./examples/21-migrations.ts) | MigrationRunner, MigrationGenerator |
|
|
328
|
+
| 22 | [related-query-builder](./examples/22-related-query-builder.ts) | `$related()` — scoped relation queries |
|
|
329
|
+
| 23 | [attach-detach-sync](./examples/23-attach-detach-sync.ts) | Many-to-many pivot management |
|
|
379
330
|
| 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) |
|
|
331
|
+
| 25 | [static-hooks](./examples/25-static-hooks.ts) | `asFindQuery()` + `cancelQuery()` |
|
|
332
|
+
| 26 | [repository-pattern](./examples/26-repository-pattern.ts) | `createRepo()` — custom query methods |
|
|
333
|
+
| 27 | [plugins-and-helpers](./examples/27-plugins-and-helpers.ts) | `.use()` plugin system + makeHelper() |
|
|
334
|
+
| 28 | [nested-create-update](./examples/28-nested-create-update.ts) | Create/update with related data in one call |
|
|
335
|
+
| 29 | [allow-graph](./examples/29-allow-graph.ts) | `allowGraph()` — recursive eager load whitelist |
|
|
336
|
+
| 30 | [polymorphic-relations](./examples/30-polymorphic-relations.ts) | MorphMany/MorphOne/MorphTo |
|
|
386
337
|
| 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 })`
|
|
338
|
+
| 32 | [accessors-mutators](./examples/32-accessors-mutators.ts) | `Attribute.make({ get, set })` |
|
|
388
339
|
|
|
389
340
|
---
|
|
390
341
|
|
|
391
|
-
##
|
|
392
|
-
|
|
393
|
-
|
|
|
394
|
-
|
|
395
|
-
|
|
|
396
|
-
|
|
|
397
|
-
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
342
|
+
## Database Support
|
|
343
|
+
|
|
344
|
+
| Database | Dialect package | Status |
|
|
345
|
+
|----------|----------------|--------|
|
|
346
|
+
| SQLite | `kysely-bun-sqlite` | ✅ Tested |
|
|
347
|
+
| PostgreSQL | `pg` | ✅ Tested via Docker |
|
|
348
|
+
| MySQL | `mysql2` | ✅ Tested via Docker |
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
docker compose up -d # PostgreSQL 16 + MySQL 8.0
|
|
352
|
+
cd packages/orm
|
|
353
|
+
bun test test/integration/
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Set `INTEGRATION_SKIP_PG=1` or `INTEGRATION_SKIP_MYSQL=1` to skip specific databases.
|
|
405
357
|
|
|
406
358
|
---
|
|
407
359
|
|
|
408
|
-
##
|
|
360
|
+
## Related packages
|
|
409
361
|
|
|
410
|
-
|
|
362
|
+
- [peta-auth](../auth) — Encrypted cookie sessions, JWT, OAuth
|
|
363
|
+
- [peta-docs](../docs) — OpenAPI 3.1 spec generation + Scalar UI
|
|
364
|
+
- [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);
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { n as createCollection } from "./collection-
|
|
2
|
+
import { n as createCollection } from "./collection-PFmrQHyM.mjs";
|
|
3
3
|
import { a as ModelNotRegisteredError, c as ValidationError, i as ModelNotFoundError, n as normalizeError, o as RelationNotAllowedError, r as DatabaseError, s as RelationNotFoundError } from "./errors-sfFJolfu.mjs";
|
|
4
4
|
import { a as registerSoftDeletesFor, n as getSoftDeleteConfig, o as registerTimestampsFor, r as hasSoftDelete, s as createHookManager, t as getHooksFor } from "./hooks-BD0xy7uw.mjs";
|
|
5
|
-
import { a as castValue, r as initRuntime, t as createInstance } from "./factory-
|
|
5
|
+
import { a as castValue, r as initRuntime, t as createInstance } from "./factory-BBvIMQuc.mjs";
|
|
6
6
|
import { a as getRawRelations, d as setExists, o as getState } from "./state-LtlHp6XV.mjs";
|
|
7
|
-
import { a as setConfig$1, n as reloadModel, r as saveModel, t as getConfig$1 } from "./save-
|
|
7
|
+
import { a as setConfig$1, n as reloadModel, r as saveModel, t as getConfig$1 } from "./save-D5UKXvqC.mjs";
|
|
8
8
|
import { type } from "arktype";
|
|
9
9
|
import { Kysely, sql } from "kysely";
|
|
10
10
|
import { ulid as ulid$1 } from "ulid";
|
|
@@ -948,7 +948,7 @@ function createQueryBuilder(def, peta) {
|
|
|
948
948
|
execute: runExecute,
|
|
949
949
|
async collect() {
|
|
950
950
|
const items = await runExecute();
|
|
951
|
-
const { createCollection } = await import("./collection-
|
|
951
|
+
const { createCollection } = await import("./collection-PFmrQHyM.mjs").then((n) => n.t);
|
|
952
952
|
return createCollection(items);
|
|
953
953
|
},
|
|
954
954
|
async executeTakeFirst() {
|
|
@@ -1396,16 +1396,16 @@ function defineModel(table, config) {
|
|
|
1396
1396
|
return this.query().first();
|
|
1397
1397
|
},
|
|
1398
1398
|
async create(data) {
|
|
1399
|
-
return (await import("./save-
|
|
1399
|
+
return (await import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertModel(def, data);
|
|
1400
1400
|
},
|
|
1401
1401
|
async insert(data) {
|
|
1402
|
-
return (await import("./save-
|
|
1402
|
+
return (await import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertModel(def, data);
|
|
1403
1403
|
},
|
|
1404
1404
|
async insertMany(dataArray) {
|
|
1405
|
-
return (await import("./save-
|
|
1405
|
+
return (await import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertManyModel(def, dataArray);
|
|
1406
1406
|
},
|
|
1407
1407
|
async update(id, data) {
|
|
1408
|
-
return (await import("./save-
|
|
1408
|
+
return (await import("./save-D5UKXvqC.mjs").then((n) => n.i)).updateModel(def, id, data);
|
|
1409
1409
|
},
|
|
1410
1410
|
async insertGraph(data, options) {
|
|
1411
1411
|
return (await Promise.resolve().then(() => graph_exports)).insertGraph(def, data, options);
|
|
@@ -2157,7 +2157,10 @@ function hasMany(relatedThunk, options = {}) {
|
|
|
2157
2157
|
function hasOne(relatedThunk, options = {}) {
|
|
2158
2158
|
const base = hasMany(relatedThunk, options);
|
|
2159
2159
|
const result = {};
|
|
2160
|
-
for (const key of Object.keys(base))
|
|
2160
|
+
for (const key of Object.keys(base)) {
|
|
2161
|
+
const desc = Object.getOwnPropertyDescriptor(base, key);
|
|
2162
|
+
Object.defineProperty(result, key, desc);
|
|
2163
|
+
}
|
|
2161
2164
|
result.match = function match(models, results, relationName) {
|
|
2162
2165
|
const grouped = groupByArray$1(results, base.foreignKey);
|
|
2163
2166
|
for (const model of models) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { n as normalizeError, r as DatabaseError } from "./errors-sfFJolfu.mjs";
|
|
3
3
|
import { t as getHooksFor } from "./hooks-BD0xy7uw.mjs";
|
|
4
|
-
import { i as applyCastsToData, o as prepareForDb, t as createInstance } from "./factory-
|
|
4
|
+
import { i as applyCastsToData, o as prepareForDb, t as createInstance } from "./factory-BBvIMQuc.mjs";
|
|
5
5
|
import { d as setExists, f as syncOriginal, i as getExists, o as getState } from "./state-LtlHp6XV.mjs";
|
|
6
6
|
//#region src/model/save.ts
|
|
7
7
|
var save_exports = /* @__PURE__ */ __exportAll({
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "peta-orm",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0",
|
|
6
6
|
"description": "ORM for Bun, built on Kysely",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ulid": "^3.0.2"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"kysely": "^0.
|
|
41
|
+
"kysely": "^0.29.2",
|
|
42
42
|
"typescript": "^6.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
"@types/bun": "^1.3.14",
|
|
47
47
|
"elysia": "^1.4.28",
|
|
48
48
|
"hono": "^4.12.25",
|
|
49
|
-
"kysely": "^0.
|
|
50
|
-
"kysely-
|
|
49
|
+
"kysely": "^0.29.2",
|
|
50
|
+
"@libsql/kysely-libsql": "^0.4.1",
|
|
51
|
+
"@libsql/client": "^0.8.0",
|
|
51
52
|
"mysql2": "^3.22.5",
|
|
52
53
|
"pg": "^8.21.0",
|
|
53
54
|
"tsdown": "^0.22.2",
|