ilana-orm 1.0.15 → 1.0.16
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 +139 -108
- package/database/schema-builder.js +18 -12
- package/index.js +1 -0
- package/index.mjs +1 -0
- package/orm/Factory.js +20 -18
- package/orm/Model.d.ts +26 -3
- package/orm/Model.js +138 -25
- package/orm/QueryBuilder.d.ts +24 -2
- package/orm/QueryBuilder.js +437 -75
- package/orm/Relation.d.ts +15 -3
- package/orm/Relation.js +74 -11
- package/orm/Relation.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ A fully-featured, Eloquent-style ORM for Node.js with automatic TypeScript suppo
|
|
|
59
59
|
- Conditional queries with `when()`
|
|
60
60
|
- Query scopes with automatic proxy support
|
|
61
61
|
- JSON queries for PostgreSQL and MySQL
|
|
62
|
-
- Date-specific queries (whereDate, whereMonth, whereYear)
|
|
62
|
+
- Date-specific queries (whereDate, whereMonth, whereYear, whereDay, whereTime) across all databases
|
|
63
63
|
|
|
64
64
|
### 🗄️ **Database Management**
|
|
65
65
|
|
|
@@ -899,8 +899,10 @@ const {
|
|
|
899
899
|
} = require('ilana-orm/orm/CustomCasts');
|
|
900
900
|
|
|
901
901
|
class Product extends Model {
|
|
902
|
+
// Cast class instances are called automatically — get() on read, set() on write
|
|
902
903
|
casts = {
|
|
903
904
|
price: new MoneyCast(),
|
|
905
|
+
// ⚠️ EncryptedCast uses base64 only — replace with real encryption for production
|
|
904
906
|
secret_data: new EncryptedCast('your-encryption-key'),
|
|
905
907
|
metadata: new JsonCast(),
|
|
906
908
|
tags: new ArrayCast(),
|
|
@@ -908,15 +910,10 @@ class Product extends Model {
|
|
|
908
910
|
};
|
|
909
911
|
}
|
|
910
912
|
|
|
911
|
-
//
|
|
912
|
-
class
|
|
913
|
-
get(
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
set(value) {
|
|
918
|
-
return value ? Math.round(value * 100) : null;
|
|
919
|
-
}
|
|
913
|
+
// Custom cast — implement get(value) and set(value)
|
|
914
|
+
class SlugCast {
|
|
915
|
+
get(v) { return v; }
|
|
916
|
+
set(v) { return v.toLowerCase().replace(/\s+/g, '-'); }
|
|
920
917
|
}
|
|
921
918
|
|
|
922
919
|
// Generate custom cast with CLI
|
|
@@ -966,13 +963,17 @@ class MoneyCast {
|
|
|
966
963
|
|
|
967
964
|
### Mutators and Accessors
|
|
968
965
|
|
|
966
|
+
**Mutators** (`setXxxAttribute`) are called automatically whenever `setAttribute` runs — on direct assignment (`user.email = x`), via `fill()`, and via `update()`. They must return the transformed value.
|
|
967
|
+
|
|
968
|
+
**Accessors** (`getXxxAttribute`) are called automatically on direct property access (`user.full_name`) as long as the key exists in `appends`, `fillable`, or the model's `attributes`. List keys in `appends` to include them in `toJSON()` output.
|
|
969
|
+
|
|
969
970
|
**JavaScript:**
|
|
970
971
|
```javascript
|
|
971
972
|
class User extends Model {
|
|
972
|
-
//
|
|
973
|
+
// Keys listed in appends appear in toJSON() and are accessible as properties
|
|
973
974
|
appends = ['full_name', 'avatar_url'];
|
|
974
975
|
|
|
975
|
-
// Mutator
|
|
976
|
+
// Mutator — called automatically on assignment / fill / update
|
|
976
977
|
setPasswordAttribute(value) {
|
|
977
978
|
return value ? bcrypt.hashSync(value, 10) : value;
|
|
978
979
|
}
|
|
@@ -981,7 +982,7 @@ class User extends Model {
|
|
|
981
982
|
return value ? value.toLowerCase().trim() : value;
|
|
982
983
|
}
|
|
983
984
|
|
|
984
|
-
// Accessor
|
|
985
|
+
// Accessor — called on direct property access and in toJSON()
|
|
985
986
|
getFullNameAttribute() {
|
|
986
987
|
return `${this.first_name} ${this.last_name}`;
|
|
987
988
|
}
|
|
@@ -993,20 +994,22 @@ class User extends Model {
|
|
|
993
994
|
}
|
|
994
995
|
}
|
|
995
996
|
|
|
996
|
-
//
|
|
997
|
+
// Direct property access triggers the accessor
|
|
997
998
|
const user = await User.find(1);
|
|
998
|
-
console.log(user.
|
|
999
|
-
//
|
|
999
|
+
console.log(user.full_name); // 'John Doe' — accessor called directly
|
|
1000
|
+
console.log(user.toJSON()); // includes full_name and avatar_url via appends
|
|
1001
|
+
|
|
1002
|
+
// Mutator is called automatically
|
|
1003
|
+
user.email = 'USER@EXAMPLE.COM';
|
|
1004
|
+
console.log(user.email); // 'user@example.com' — mutator ran on assignment
|
|
1000
1005
|
````
|
|
1001
1006
|
|
|
1002
1007
|
**TypeScript:**
|
|
1003
1008
|
|
|
1004
1009
|
```typescript
|
|
1005
1010
|
class User extends Model {
|
|
1006
|
-
// Appended attributes (automatically included in JSON)
|
|
1007
1011
|
protected appends: string[] = ["full_name", "avatar_url"];
|
|
1008
1012
|
|
|
1009
|
-
// Mutator - transform data when setting
|
|
1010
1013
|
setPasswordAttribute(value: string) {
|
|
1011
1014
|
return value ? bcrypt.hashSync(value, 10) : value;
|
|
1012
1015
|
}
|
|
@@ -1015,7 +1018,6 @@ class User extends Model {
|
|
|
1015
1018
|
return value ? value.toLowerCase().trim() : value;
|
|
1016
1019
|
}
|
|
1017
1020
|
|
|
1018
|
-
// Accessor - transform data when getting
|
|
1019
1021
|
getFullNameAttribute(): string {
|
|
1020
1022
|
return `${this.first_name} ${this.last_name}`;
|
|
1021
1023
|
}
|
|
@@ -1027,10 +1029,9 @@ class User extends Model {
|
|
|
1027
1029
|
}
|
|
1028
1030
|
}
|
|
1029
1031
|
|
|
1030
|
-
// Usage
|
|
1031
1032
|
const user = await User.find(1);
|
|
1032
|
-
console.log(user.
|
|
1033
|
-
//
|
|
1033
|
+
console.log(user.full_name); // accessor called directly
|
|
1034
|
+
console.log(user.toJSON()); // includes appended accessors
|
|
1034
1035
|
```
|
|
1035
1036
|
|
|
1036
1037
|
````
|
|
@@ -1621,6 +1622,31 @@ const users = await User.query()
|
|
|
1621
1622
|
})
|
|
1622
1623
|
.get();
|
|
1623
1624
|
|
|
1625
|
+
// Relation existence (WHERE EXISTS subquery, with optional constraint)
|
|
1626
|
+
const usersWithPosts = await User.query().whereHas('posts').get();
|
|
1627
|
+
const activeAuthors = await User.query()
|
|
1628
|
+
.whereHas('posts', (q) => q.where('published', true))
|
|
1629
|
+
.get();
|
|
1630
|
+
const usersWithoutPosts = await User.query().whereDoesntHave('posts').get();
|
|
1631
|
+
|
|
1632
|
+
// OR variants
|
|
1633
|
+
const users = await User.query()
|
|
1634
|
+
.whereNull('verified_at')
|
|
1635
|
+
.orWhereNull('deleted_at')
|
|
1636
|
+
.orWhereNotNull('banned_at')
|
|
1637
|
+
.orWhereIn('role', ['admin', 'mod'])
|
|
1638
|
+
.orWhereRaw('score > ?', [100])
|
|
1639
|
+
.get();
|
|
1640
|
+
|
|
1641
|
+
// Range
|
|
1642
|
+
const minors = await User.query().whereNotBetween('age', [18, 120]).get();
|
|
1643
|
+
|
|
1644
|
+
// Date helpers (DAY/TIME use MySQL-style raw SQL functions)
|
|
1645
|
+
const users = await User.query()
|
|
1646
|
+
.whereDay('created_at', 15)
|
|
1647
|
+
.whereTime('created_at', '>', '08:00:00')
|
|
1648
|
+
.get();
|
|
1649
|
+
|
|
1624
1650
|
// Conditional queries
|
|
1625
1651
|
const users = await User.query()
|
|
1626
1652
|
.when(filters.role, (query, role) => {
|
|
@@ -1641,32 +1667,14 @@ const users = await User.query()
|
|
|
1641
1667
|
.where("age", ">", 18)
|
|
1642
1668
|
.get();
|
|
1643
1669
|
|
|
1644
|
-
// Where
|
|
1645
|
-
const users = await User.query()
|
|
1646
|
-
.where("age", ">=", 21)
|
|
1647
|
-
.where("name", "like", "%john%")
|
|
1648
|
-
.get();
|
|
1649
|
-
|
|
1650
|
-
// Or where
|
|
1651
|
-
const users = await User.query()
|
|
1652
|
-
.where("role", "admin")
|
|
1653
|
-
.orWhere("role", "moderator")
|
|
1654
|
-
.get();
|
|
1655
|
-
|
|
1656
|
-
// Where in
|
|
1670
|
+
// Where in / null / between
|
|
1657
1671
|
const users = await User.query()
|
|
1658
1672
|
.whereIn("role", ["admin", "editor", "author"])
|
|
1659
|
-
.get();
|
|
1660
|
-
|
|
1661
|
-
// Where null/not null
|
|
1662
|
-
const users = await User.query()
|
|
1663
1673
|
.whereNull("deleted_at")
|
|
1664
|
-
.
|
|
1674
|
+
.whereBetween("age", [18, 65])
|
|
1675
|
+
.whereNotBetween("score", [0, 10])
|
|
1665
1676
|
.get();
|
|
1666
1677
|
|
|
1667
|
-
// Where between
|
|
1668
|
-
const users = await User.query().whereBetween("age", [18, 65]).get();
|
|
1669
|
-
|
|
1670
1678
|
// JSON queries (database-specific)
|
|
1671
1679
|
const users = await User.query()
|
|
1672
1680
|
.whereJsonContains("preferences", { theme: "dark" })
|
|
@@ -1678,23 +1686,21 @@ const users = await User.query()
|
|
|
1678
1686
|
.whereDate("created_at", "2023-12-01")
|
|
1679
1687
|
.whereMonth("created_at", 12)
|
|
1680
1688
|
.whereYear("created_at", 2023)
|
|
1689
|
+
.whereDay("created_at", 15)
|
|
1690
|
+
.whereTime("created_at", ">", "08:00:00")
|
|
1681
1691
|
.get();
|
|
1682
1692
|
|
|
1683
|
-
//
|
|
1684
|
-
const
|
|
1685
|
-
.
|
|
1686
|
-
query.select("*").from("posts").whereRaw("posts.user_id = users.id");
|
|
1687
|
-
})
|
|
1693
|
+
// Relation existence
|
|
1694
|
+
const authors = await User.query()
|
|
1695
|
+
.whereHas("posts", (q) => q.where("published", true))
|
|
1688
1696
|
.get();
|
|
1697
|
+
const lurkers = await User.query().whereDoesntHave("posts").get();
|
|
1689
1698
|
|
|
1690
1699
|
// Conditional queries
|
|
1691
1700
|
const users = await User.query()
|
|
1692
1701
|
.when(filters.role, (query, role) => {
|
|
1693
1702
|
query.where("role", role);
|
|
1694
1703
|
})
|
|
1695
|
-
.when(filters.search, (query, search) => {
|
|
1696
|
-
query.where("name", "like", `%${search}%`);
|
|
1697
|
-
})
|
|
1698
1704
|
.get();
|
|
1699
1705
|
```
|
|
1700
1706
|
|
|
@@ -1756,8 +1762,13 @@ const roleStats = await User.query()
|
|
|
1756
1762
|
const users = await User.query()
|
|
1757
1763
|
.orderBy('name')
|
|
1758
1764
|
.orderBy('created_at', 'desc')
|
|
1765
|
+
.latest() // shorthand: orderBy('created_at', 'desc')
|
|
1766
|
+
.oldest() // shorthand: orderBy('created_at', 'asc')
|
|
1767
|
+
.inRandomOrder() // RAND() on MySQL, RANDOM() on PostgreSQL/SQLite
|
|
1759
1768
|
.limit(10)
|
|
1760
1769
|
.offset(20)
|
|
1770
|
+
.take(10).skip(20) // aliases for limit/offset
|
|
1771
|
+
.forPage(3, 15) // offset((3-1)*15).limit(15)
|
|
1761
1772
|
.get();
|
|
1762
1773
|
````
|
|
1763
1774
|
|
|
@@ -1766,9 +1777,8 @@ const users = await User.query()
|
|
|
1766
1777
|
```typescript
|
|
1767
1778
|
const users = await User.query()
|
|
1768
1779
|
.orderBy("name")
|
|
1769
|
-
.
|
|
1770
|
-
.
|
|
1771
|
-
.offset(20)
|
|
1780
|
+
.inRandomOrder()
|
|
1781
|
+
.forPage(2, 20)
|
|
1772
1782
|
.get();
|
|
1773
1783
|
```
|
|
1774
1784
|
|
|
@@ -1927,13 +1937,26 @@ const roles = user.roles;
|
|
|
1927
1937
|
roles.forEach((role) => {
|
|
1928
1938
|
console.log(role.pivot.assigned_at);
|
|
1929
1939
|
});
|
|
1940
|
+
|
|
1941
|
+
// Pivot management
|
|
1942
|
+
const rel = user.roles();
|
|
1943
|
+
await rel.attach(roleId);
|
|
1944
|
+
await rel.attach(roleId, { assigned_at: new Date() }); // with pivot attributes
|
|
1945
|
+
await rel.detach(roleId);
|
|
1946
|
+
await rel.detach(); // detach all
|
|
1947
|
+
|
|
1948
|
+
await rel.sync([1, 2, 3]); // detach all, then attach these IDs
|
|
1949
|
+
await rel.sync({ 1: { assigned_at: new Date() }, 2: {} }); // with pivot attrs per ID
|
|
1950
|
+
|
|
1951
|
+
await rel.toggle([1, 2]); // attach if missing, detach if present
|
|
1952
|
+
|
|
1953
|
+
await rel.updateExistingPivot(roleId, { assigned_at: new Date() }); // update pivot row
|
|
1930
1954
|
````
|
|
1931
1955
|
|
|
1932
1956
|
**TypeScript:**
|
|
1933
1957
|
|
|
1934
1958
|
```typescript
|
|
1935
1959
|
class User extends Model {
|
|
1936
|
-
// User belongs to many roles
|
|
1937
1960
|
roles() {
|
|
1938
1961
|
return this.belongsToMany(Role, "user_roles", "user_id", "role_id")
|
|
1939
1962
|
.withPivot("assigned_at", "assigned_by")
|
|
@@ -1941,73 +1964,75 @@ class User extends Model {
|
|
|
1941
1964
|
}
|
|
1942
1965
|
}
|
|
1943
1966
|
|
|
1944
|
-
class Role extends Model {
|
|
1945
|
-
// Role belongs to many users
|
|
1946
|
-
users() {
|
|
1947
|
-
return this.belongsToMany(User, "user_roles", "role_id", "user_id");
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// Usage
|
|
1952
|
-
const user = await User.with("roles").first();
|
|
1953
|
-
const roles = user.roles;
|
|
1954
|
-
|
|
1955
1967
|
// Access pivot data
|
|
1956
|
-
roles.
|
|
1968
|
+
const user = await User.with("roles").first();
|
|
1969
|
+
user.roles.forEach((role) => {
|
|
1957
1970
|
console.log(role.pivot.assigned_at);
|
|
1958
1971
|
});
|
|
1972
|
+
|
|
1973
|
+
// Pivot management
|
|
1974
|
+
const rel = user.roles();
|
|
1975
|
+
await rel.sync([1, 2, 3]);
|
|
1976
|
+
await rel.toggle(roleId);
|
|
1977
|
+
await rel.updateExistingPivot(roleId, { assigned_at: new Date() });
|
|
1959
1978
|
```
|
|
1960
1979
|
|
|
1961
1980
|
````
|
|
1962
1981
|
|
|
1963
1982
|
### Polymorphic Relationships
|
|
1964
1983
|
|
|
1984
|
+
Use string names for related models to avoid circular import issues. Every model involved in a polymorphic relation must call `static { this.register(); }`.
|
|
1985
|
+
|
|
1965
1986
|
**JavaScript:**
|
|
1966
1987
|
```javascript
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
return this.morphTo('
|
|
1988
|
+
// One-to-one polymorphic (morphOne / morphTo)
|
|
1989
|
+
class Image extends Model {
|
|
1990
|
+
imageable() {
|
|
1991
|
+
return this.morphTo('imageable'); // reads imageable_type, imageable_id
|
|
1971
1992
|
}
|
|
1993
|
+
static { this.register(); }
|
|
1972
1994
|
}
|
|
1973
1995
|
|
|
1974
1996
|
class Post extends Model {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
return this.morphMany(Comment, 'commentable');
|
|
1997
|
+
image() {
|
|
1998
|
+
return this.morphOne('Image', 'imageable'); // stores imageable_type='Post', imageable_id=post.id
|
|
1978
1999
|
}
|
|
2000
|
+
static { this.register(); }
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// One-to-many polymorphic (morphMany / morphTo)
|
|
2004
|
+
class Comment extends Model {
|
|
2005
|
+
commentable() {
|
|
2006
|
+
return this.morphTo('commentable');
|
|
2007
|
+
}
|
|
2008
|
+
static { this.register(); }
|
|
1979
2009
|
}
|
|
1980
2010
|
|
|
1981
2011
|
class Video extends Model {
|
|
1982
|
-
// Video has many comments (polymorphic)
|
|
1983
2012
|
comments() {
|
|
1984
|
-
return this.morphMany(Comment, 'commentable');
|
|
2013
|
+
return this.morphMany('Comment', 'commentable');
|
|
1985
2014
|
}
|
|
2015
|
+
static { this.register(); }
|
|
1986
2016
|
}
|
|
1987
2017
|
````
|
|
1988
2018
|
|
|
1989
2019
|
**TypeScript:**
|
|
1990
2020
|
|
|
1991
2021
|
```typescript
|
|
1992
|
-
class
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
return this.morphTo("commentable");
|
|
1996
|
-
}
|
|
2022
|
+
class Image extends Model {
|
|
2023
|
+
imageable() { return this.morphTo("imageable"); }
|
|
2024
|
+
static { this.register(); }
|
|
1997
2025
|
}
|
|
1998
2026
|
|
|
1999
2027
|
class Post extends Model {
|
|
2000
|
-
|
|
2001
|
-
comments() {
|
|
2002
|
-
|
|
2003
|
-
}
|
|
2028
|
+
image() { return this.morphOne("Image", "imageable"); }
|
|
2029
|
+
comments() { return this.morphMany("Comment", "commentable"); }
|
|
2030
|
+
static { this.register(); }
|
|
2004
2031
|
}
|
|
2005
2032
|
|
|
2006
|
-
class
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
return this.morphMany(Comment, "commentable");
|
|
2010
|
-
}
|
|
2033
|
+
class Comment extends Model {
|
|
2034
|
+
commentable() { return this.morphTo("commentable"); }
|
|
2035
|
+
static { this.register(); }
|
|
2011
2036
|
}
|
|
2012
2037
|
```
|
|
2013
2038
|
|
|
@@ -3317,9 +3342,9 @@ export default defineFactory(User, () => ({
|
|
|
3317
3342
|
email_verified_at: faker.date.past(),
|
|
3318
3343
|
phone_verified_at: faker.date.past(),
|
|
3319
3344
|
}))
|
|
3320
|
-
.state("with_profile", () => ({}))
|
|
3321
|
-
.afterCreating(async (user
|
|
3322
|
-
if (
|
|
3345
|
+
.state("with_profile", () => ({ _with_profile: true }))
|
|
3346
|
+
.afterCreating(async (user) => {
|
|
3347
|
+
if (user.attributes._with_profile) {
|
|
3323
3348
|
await user.profile().create({
|
|
3324
3349
|
bio: faker.lorem.paragraph(),
|
|
3325
3350
|
website: faker.internet.url(),
|
|
@@ -4079,9 +4104,9 @@ class User extends Model {
|
|
|
4079
4104
|
// Hide sensitive data
|
|
4080
4105
|
protected hidden = ["password", "remember_token"];
|
|
4081
4106
|
|
|
4082
|
-
// Use mutators for sensitive data
|
|
4107
|
+
// Use mutators for sensitive data — mutators must return the transformed value
|
|
4083
4108
|
setPasswordAttribute(value: string) {
|
|
4084
|
-
|
|
4109
|
+
return bcrypt.hashSync(value, 10);
|
|
4085
4110
|
}
|
|
4086
4111
|
}
|
|
4087
4112
|
|
|
@@ -4255,8 +4280,8 @@ User.fireEvent(event, model); // Fire event
|
|
|
4255
4280
|
// Scopes
|
|
4256
4281
|
User.addGlobalScope(name, scope); // Add global scope
|
|
4257
4282
|
User.removeGlobalScope(name); // Remove global scope
|
|
4258
|
-
User.withoutGlobalScope(name); // Query without scope
|
|
4259
|
-
User.
|
|
4283
|
+
User.withoutGlobalScope(name); // Query without one named scope
|
|
4284
|
+
User.withoutGlobalScopes(); // Query bypassing all global scopes
|
|
4260
4285
|
|
|
4261
4286
|
// Factory
|
|
4262
4287
|
User.factory(); // Get factory instance
|
|
@@ -4270,15 +4295,13 @@ User.register(); // Register in registry
|
|
|
4270
4295
|
user.save(); // Save model
|
|
4271
4296
|
user.update(attributes); // Update model
|
|
4272
4297
|
user.delete(); // Delete model
|
|
4273
|
-
user.forceDelete(); // Force delete
|
|
4274
|
-
user.restore(); // Restore soft deleted
|
|
4275
|
-
user.forceDelete(); // Force delete
|
|
4298
|
+
user.forceDelete(); // Force delete (ignores softDeletes)
|
|
4276
4299
|
user.restore(); // Restore soft deleted
|
|
4277
4300
|
|
|
4278
4301
|
// Attributes
|
|
4279
|
-
user.fill(attributes); // Mass assign
|
|
4280
|
-
user.getAttribute(key); // Get attribute
|
|
4281
|
-
user.setAttribute(key, value); // Set attribute
|
|
4302
|
+
user.fill(attributes); // Mass assign (respects fillable/guarded)
|
|
4303
|
+
user.getAttribute(key); // Get attribute (calls accessor if defined)
|
|
4304
|
+
user.setAttribute(key, value); // Set attribute (calls mutator if defined)
|
|
4282
4305
|
user.getKey(); // Get primary key value
|
|
4283
4306
|
user.isFillable(key); // Check if fillable
|
|
4284
4307
|
user.isDirty(key); // Check if dirty
|
|
@@ -4288,7 +4311,9 @@ user.syncOriginal(); // Sync original
|
|
|
4288
4311
|
user.only(keys); // Get only specified attributes
|
|
4289
4312
|
user.except(keys); // Get all except specified
|
|
4290
4313
|
user.trashed(); // Check if soft deleted
|
|
4291
|
-
user.
|
|
4314
|
+
user.makeHidden(keys); // Add keys to hidden list
|
|
4315
|
+
user.makeVisible(keys); // Remove keys from hidden list
|
|
4316
|
+
user.append(keys); // Add accessor keys to appends
|
|
4292
4317
|
|
|
4293
4318
|
// Relations
|
|
4294
4319
|
user.load(...relations); // Lazy load relations
|
|
@@ -4314,8 +4339,12 @@ query.where(column, value); // Equals operator
|
|
|
4314
4339
|
query.orWhere(column, operator, value);
|
|
4315
4340
|
query.whereIn(column, values);
|
|
4316
4341
|
query.whereNotIn(column, values);
|
|
4342
|
+
query.orWhereIn(column, values);
|
|
4343
|
+
query.orWhereNotIn(column, values);
|
|
4317
4344
|
query.whereNull(column);
|
|
4318
4345
|
query.whereNotNull(column);
|
|
4346
|
+
query.orWhereNull(column);
|
|
4347
|
+
query.orWhereNotNull(column);
|
|
4319
4348
|
query.whereBetween(column, [min, max]);
|
|
4320
4349
|
query.whereNotBetween(column, [min, max]);
|
|
4321
4350
|
query.whereRaw(sql, bindings);
|
|
@@ -4357,6 +4386,7 @@ query.innerJoin(table, first, operator, second);
|
|
|
4357
4386
|
|
|
4358
4387
|
```javascript
|
|
4359
4388
|
query.orderBy(column, direction);
|
|
4389
|
+
query.orderByRaw(sql);
|
|
4360
4390
|
query.latest(column);
|
|
4361
4391
|
query.oldest(column);
|
|
4362
4392
|
query.inRandomOrder();
|
|
@@ -4438,10 +4468,10 @@ query.upsert(data, uniqueBy, update);
|
|
|
4438
4468
|
```javascript
|
|
4439
4469
|
query.with(...relations);
|
|
4440
4470
|
query.withConstraints(relation, callback);
|
|
4441
|
-
query.withCount(...relations);
|
|
4442
|
-
query.whereHas(relation, callback);
|
|
4443
|
-
query.
|
|
4444
|
-
query.
|
|
4471
|
+
query.withCount(...relations); // adds relation_count subquery column per model
|
|
4472
|
+
query.whereHas(relation, callback); // WHERE EXISTS subquery
|
|
4473
|
+
query.doesntHave(relation); // WHERE NOT EXISTS subquery
|
|
4474
|
+
query.whereDoesntHave(relation, callback); // WHERE NOT EXISTS with constraint
|
|
4445
4475
|
```
|
|
4446
4476
|
|
|
4447
4477
|
#### Locking
|
|
@@ -4489,6 +4519,7 @@ this.hasManyThrough(
|
|
|
4489
4519
|
|
|
4490
4520
|
// Polymorphic
|
|
4491
4521
|
this.morphTo(morphType, morphId);
|
|
4522
|
+
this.morphOne(related, morphType, morphId);
|
|
4492
4523
|
this.morphMany(related, morphType, morphId);
|
|
4493
4524
|
```
|
|
4494
4525
|
|
|
@@ -104,10 +104,9 @@ class SchemaBuilder {
|
|
|
104
104
|
|
|
105
105
|
checkPositive(column) {
|
|
106
106
|
const client = this.knex.client.config.client;
|
|
107
|
-
if (client === 'pg') {
|
|
108
|
-
return this.knex.raw(`ALTER TABLE
|
|
109
|
-
|
|
110
|
-
return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_positive CHECK (${column} > 0)`);
|
|
107
|
+
if (client === 'pg' || client === 'mysql2') {
|
|
108
|
+
return this.knex.raw(`ALTER TABLE ?? ADD CONSTRAINT ?? CHECK (?? > 0)`,
|
|
109
|
+
[this.currentTable, `${column}_positive`, column]);
|
|
111
110
|
}
|
|
112
111
|
return Promise.resolve();
|
|
113
112
|
}
|
|
@@ -115,9 +114,11 @@ class SchemaBuilder {
|
|
|
115
114
|
checkRegex(column, pattern) {
|
|
116
115
|
const client = this.knex.client.config.client;
|
|
117
116
|
if (client === 'pg') {
|
|
118
|
-
return this.knex.raw(`ALTER TABLE
|
|
117
|
+
return this.knex.raw(`ALTER TABLE ?? ADD CONSTRAINT ?? CHECK (?? ~ ?)`,
|
|
118
|
+
[this.currentTable, `${column}_regex`, column, pattern]);
|
|
119
119
|
} else if (client === 'mysql2') {
|
|
120
|
-
return this.knex.raw(`ALTER TABLE
|
|
120
|
+
return this.knex.raw(`ALTER TABLE ?? ADD CONSTRAINT ?? CHECK (?? REGEXP ?)`,
|
|
121
|
+
[this.currentTable, `${column}_regex`, column, pattern]);
|
|
121
122
|
}
|
|
122
123
|
return Promise.resolve();
|
|
123
124
|
}
|
|
@@ -125,9 +126,11 @@ class SchemaBuilder {
|
|
|
125
126
|
generatedAs(column, expression) {
|
|
126
127
|
const client = this.knex.client.config.client;
|
|
127
128
|
if (client === 'mysql2') {
|
|
128
|
-
return this.knex.raw(`ALTER TABLE
|
|
129
|
+
return this.knex.raw(`ALTER TABLE ?? ADD ?? VARCHAR(255) GENERATED ALWAYS AS (${expression}) STORED`,
|
|
130
|
+
[this.currentTable, column]);
|
|
129
131
|
} else if (client === 'pg') {
|
|
130
|
-
return this.knex.raw(`ALTER TABLE
|
|
132
|
+
return this.knex.raw(`ALTER TABLE ?? ADD ?? TEXT GENERATED ALWAYS AS (${expression}) STORED`,
|
|
133
|
+
[this.currentTable, column]);
|
|
131
134
|
}
|
|
132
135
|
return Promise.resolve();
|
|
133
136
|
}
|
|
@@ -135,9 +138,9 @@ class SchemaBuilder {
|
|
|
135
138
|
collate(tableName, collation) {
|
|
136
139
|
const client = this.knex.client.config.client;
|
|
137
140
|
if (client === 'mysql2') {
|
|
138
|
-
return this.knex.raw(`ALTER TABLE
|
|
141
|
+
return this.knex.raw(`ALTER TABLE ?? COLLATE ??`, [tableName, collation]);
|
|
139
142
|
} else if (client === 'pg') {
|
|
140
|
-
return this.knex.raw(`ALTER TABLE
|
|
143
|
+
return this.knex.raw(`ALTER TABLE ?? ALTER COLUMN name TYPE TEXT COLLATE ??`, [tableName, collation]);
|
|
141
144
|
}
|
|
142
145
|
return Promise.resolve();
|
|
143
146
|
}
|
|
@@ -166,7 +169,9 @@ class SchemaBuilder {
|
|
|
166
169
|
fulltext(columns, indexName) {
|
|
167
170
|
if (this.knex.client.config.client === 'mysql2') {
|
|
168
171
|
const name = indexName || `${columns.join('_')}_fulltext`;
|
|
169
|
-
|
|
172
|
+
const colRefs = columns.map(() => '??').join(', ');
|
|
173
|
+
return this.knex.raw(`ALTER TABLE ?? ADD FULLTEXT INDEX ?? (${colRefs})`,
|
|
174
|
+
[this.currentTable, name, ...columns]);
|
|
170
175
|
}
|
|
171
176
|
return Promise.resolve();
|
|
172
177
|
}
|
|
@@ -174,7 +179,8 @@ class SchemaBuilder {
|
|
|
174
179
|
spatial(column, indexName) {
|
|
175
180
|
if (this.knex.client.config.client === 'mysql2') {
|
|
176
181
|
const name = indexName || `${column}_spatial`;
|
|
177
|
-
return this.knex.raw(`ALTER TABLE
|
|
182
|
+
return this.knex.raw(`ALTER TABLE ?? ADD SPATIAL INDEX ?? (??)`,
|
|
183
|
+
[this.currentTable, name, column]);
|
|
178
184
|
}
|
|
179
185
|
return Promise.resolve();
|
|
180
186
|
}
|
package/index.js
CHANGED