@typetron/framework 0.5.3 → 0.5.5
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/.eslintrc-old.js +208 -0
- package/Cache/Cache.ts +13 -0
- package/Cache/Stores/DatabaseStore.ts +120 -0
- package/Cache/Stores/FileStore.ts +69 -0
- package/Cache/Stores/MemoryStore.ts +71 -0
- package/Cache/index.ts +5 -0
- package/Cache/types.ts +4 -0
- package/Container/ClassResolver.ts +106 -0
- package/Container/Container.ts +144 -0
- package/Container/Contracts.ts +3 -0
- package/Container/Decorators.ts +20 -0
- package/Container/Metadata.ts +16 -0
- package/Container/Resolver.ts +27 -0
- package/Container/index.ts +5 -0
- package/Database/Clauses/Where.ts +0 -0
- package/Database/Connection.ts +52 -0
- package/Database/Decorators.ts +142 -0
- package/Database/Drivers/DatabaseDriver.ts +37 -0
- package/Database/Drivers/MySQL/Alter/index.ts +55 -0
- package/Database/Drivers/MySQL/ColumnDefinition.ts +16 -0
- package/Database/Drivers/MySQL/Create.ts +47 -0
- package/Database/Drivers/MySQL/MysqlDriver.ts +123 -0
- package/Database/Drivers/MySQL/Schema.ts +82 -0
- package/Database/Drivers/MySQL/index.ts +13 -0
- package/Database/Drivers/SQL/Schema.ts +73 -0
- package/Database/Drivers/SQL/Statements/Alter.ts +22 -0
- package/Database/Drivers/SQL/Statements/BaseStatement.ts +55 -0
- package/Database/Drivers/SQL/Statements/Delete.ts +20 -0
- package/Database/Drivers/SQL/Statements/Insert.ts +24 -0
- package/Database/Drivers/SQL/Statements/Select.ts +55 -0
- package/Database/Drivers/SQL/Statements/Update.ts +33 -0
- package/Database/Drivers/SQL/index.ts +8 -0
- package/Database/Drivers/SQLite/Create.ts +45 -0
- package/Database/Drivers/SQLite/Schema.ts +54 -0
- package/Database/Drivers/SQLite/SqliteDriver.ts +144 -0
- package/Database/Drivers/SQLite/index.ts +15 -0
- package/Database/Drivers/SchemaContract.ts +11 -0
- package/Database/Drivers/Statement.ts +16 -0
- package/Database/Drivers/index.ts +4 -0
- package/Database/Entity.ts +377 -0
- package/Database/EntityNotFoundError.ts +5 -0
- package/Database/EntityProxyHandler.ts +43 -0
- package/Database/EntityQuery.ts +197 -0
- package/Database/Expression.ts +3 -0
- package/Database/Fields.ts +816 -0
- package/Database/Helpers.ts +12 -0
- package/Database/List.ts +71 -0
- package/Database/Migrations/Migration.ts +10 -0
- package/Database/Migrations/MigrationHistory.ts +20 -0
- package/Database/Migrations/Migrator.ts +122 -0
- package/Database/Migrations/index.ts +3 -0
- package/Database/ORM/BaseRelationship.ts +10 -0
- package/Database/Query.ts +374 -0
- package/Database/Seeders/Seeder.ts +3 -0
- package/Database/Seeders/SeederManager.ts +27 -0
- package/Database/Seeders/index.ts +2 -0
- package/Database/StringExpression.ts +9 -0
- package/Database/Types.ts +165 -0
- package/Database/index.ts +23 -0
- package/Encryption/index.ts +51 -0
- package/Forms/Decorators.ts +29 -0
- package/Forms/Form.ts +70 -0
- package/Forms/FormFields.ts +36 -0
- package/Forms/index.ts +3 -0
- package/Framework/App.ts +10 -0
- package/Framework/Application.ts +118 -0
- package/Framework/Auth/Auth.ts +122 -0
- package/Framework/Auth/Authenticatable.ts +9 -0
- package/Framework/Auth/User.ts +27 -0
- package/Framework/Auth/index.ts +29 -0
- package/Framework/Config/AppConfig.ts +25 -0
- package/Framework/Config/AuthConfig.ts +9 -0
- package/Framework/Config/BaseConfig.ts +9 -0
- package/Framework/Config/CacheConfig.ts +38 -0
- package/Framework/Config/DatabaseConfig.ts +21 -0
- package/Framework/Config/MailConfig.ts +30 -0
- package/Framework/Config/index.ts +6 -0
- package/Framework/Middleware/AuthMiddleware.ts +31 -0
- package/Framework/Middleware/CorsMiddleware.ts +32 -0
- package/Framework/Middleware/StaticAssetsMiddleware.ts +76 -0
- package/Framework/Middleware/index.ts +2 -0
- package/Framework/Provider.ts +9 -0
- package/Framework/Providers/CacheProvider.ts +28 -0
- package/Framework/Providers/DatabaseProvider.ts +36 -0
- package/Framework/Providers/MailProvider.ts +31 -0
- package/Framework/Providers/WebsocketsProvider.ts +120 -0
- package/Framework/Resolvers/AuthResolver.ts +23 -0
- package/Framework/Resolvers/EntityResolver.ts +41 -0
- package/Framework/Resolvers/FormResolver.ts +37 -0
- package/Framework/RootDir.ts +1 -0
- package/Framework/index.ts +8 -0
- package/Frontend/Angular/index.ts +24 -0
- package/Frontend/Websockets/index.ts +86 -0
- package/Mail/MailAddress.ts +4 -0
- package/Mail/MailTransport.ts +6 -0
- package/Mail/Mailable.ts +16 -0
- package/Mail/Mailer.ts +27 -0
- package/Mail/PendingMail.ts +68 -0
- package/Mail/SentMessage.ts +20 -0
- package/Mail/Transports/MemoryTransport.ts +13 -0
- package/Mail/Transports/SendGridTransport.ts +95 -0
- package/Mail/index.ts +3 -0
- package/Models/Model.ts +41 -0
- package/Models/index.ts +54 -0
- package/Router/BaseController.ts +7 -0
- package/Router/Decorators.ts +224 -0
- package/Router/Guard.ts +10 -0
- package/Router/Http/Contracts.ts +4 -0
- package/Router/Http/ErrorHandler.ts +45 -0
- package/Router/Http/ErrorHandlerInterface.ts +6 -0
- package/Router/Http/Errors/HttpError.ts +15 -0
- package/Router/Http/Handler.ts +183 -0
- package/Router/Http/HttpRoute.ts +110 -0
- package/Router/Http/Middleware.ts +7 -0
- package/Router/Http/Request.ts +225 -0
- package/Router/Http/Response.ts +35 -0
- package/Router/Http/index.ts +85 -0
- package/Router/Metadata.ts +34 -0
- package/Router/Middleware.ts +16 -0
- package/Router/Request.ts +65 -0
- package/Router/Route.ts +16 -0
- package/Router/RouteNotFoundError.ts +5 -0
- package/Router/Router.ts +35 -0
- package/Router/Servers/index.ts +6 -0
- package/Router/Servers/node.ts +49 -0
- package/Router/Servers/uNetworking.ts +151 -0
- package/Router/Websockets/Handler.ts +82 -0
- package/Router/Websockets/Middleware.ts +7 -0
- package/Router/Websockets/Request.ts +3 -0
- package/Router/Websockets/WebsocketRoute.ts +61 -0
- package/Router/Websockets/index.ts +57 -0
- package/Router/Websockets/types.ts +23 -0
- package/Router/index.ts +6 -0
- package/Storage/File.ts +30 -0
- package/Storage/Image.ts +19 -0
- package/Storage/InvalidImage.ts +3 -0
- package/Storage/Storage.ts +98 -0
- package/Storage/index.ts +4 -0
- package/Storage/utils.ts +18 -0
- package/Support/Array.ts +184 -0
- package/Support/Math.ts +12 -0
- package/Support/Metadata.ts +31 -0
- package/Support/String.ts +38 -0
- package/Support/index.ts +19 -0
- package/Support/utils.ts +7 -0
- package/Testing/TestCase.ts +193 -0
- package/Validation/Rule.ts +9 -0
- package/Validation/RuleInterface.ts +10 -0
- package/Validation/Rules/Email.ts +20 -0
- package/Validation/Rules/FileExtension.ts +21 -0
- package/Validation/Rules/MaxLength.ts +17 -0
- package/Validation/Rules/MinLength.ts +18 -0
- package/Validation/Rules/Optional.ts +18 -0
- package/Validation/Rules/Required.ts +29 -0
- package/Validation/Validator.ts +7 -0
- package/Validation/index.ts +12 -0
- package/benchmark-index.js +20 -0
- package/benchmark.ts +47 -0
- package/package.json +7 -3
- package/tests/Cache/DatabaseStoreTest.ts +110 -0
- package/tests/Cache/FileStoreTest.ts +93 -0
- package/tests/Cache/MemoryStoreTest.ts +92 -0
- package/tests/Container/ContainerTest.ts +336 -0
- package/tests/Database/Drivers/MySQL/AlterTest.ts +100 -0
- package/tests/Database/Drivers/MySQL/CreateTest.ts +39 -0
- package/tests/Database/Drivers/MysqlDriverTest.ts +140 -0
- package/tests/Database/Drivers/SqliteDriverTest.ts +126 -0
- package/tests/Database/Entities/Article.ts +21 -0
- package/tests/Database/Entities/Profile.ts +21 -0
- package/tests/Database/Entities/Role.ts +15 -0
- package/tests/Database/Entities/User.ts +35 -0
- package/tests/Database/EntityRelationshipsTest.ts +475 -0
- package/tests/Database/EntityTest.ts +202 -0
- package/tests/Database/Migrations/RunnerTest.ts +96 -0
- package/tests/Database/Migrations/migrations/1.createUserTable.ts +12 -0
- package/tests/Database/Migrations/migrations/2.createArticlesTable.ts +12 -0
- package/tests/Database/QueryTest.ts +573 -0
- package/tests/Database/SchemaTest.ts +32 -0
- package/tests/Database/Seeders/RunnerTest.ts +30 -0
- package/tests/Database/Seeders/seeders/RandomSeeder.ts +10 -0
- package/tests/Database/utils.ts +16 -0
- package/tests/Forms/FormsTest.ts +59 -0
- package/tests/Framework/Middleware/StaticAssetsMiddlewareTest.ts +129 -0
- package/tests/Framework/Resolvers/EntityResolverTest.ts +48 -0
- package/tests/Framework/Router/RouterTest.ts +33 -0
- package/tests/Mail/MailerTest.ts +76 -0
- package/tests/Model/ModelTest.ts +183 -0
- package/tests/Router/HttpHandlerTest.ts +72 -0
- package/tests/Router/RequestTest.ts +77 -0
- package/tests/Router/WebSocketsHandlerTest.ts +51 -0
- package/tests/Storage/StorageTest.ts +97 -0
- package/tests/Support/ArrayTest.ts +167 -0
- package/tests/Support/StringTest.ts +18 -0
- package/tests/Validation/RulesTest.ts +54 -0
- package/types.d.ts +8 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import { Entity } from './Entity'
|
|
2
|
+
import { EntityConstructor, EntityKeys, EntityMetadata, EntityObject, ID, Query, StringExpression } from './index'
|
|
3
|
+
import { List } from './List'
|
|
4
|
+
import { BaseRelationship } from './ORM/BaseRelationship'
|
|
5
|
+
import { wrap } from './Helpers'
|
|
6
|
+
import { EntityQuery } from './EntityQuery'
|
|
7
|
+
|
|
8
|
+
export interface EntityField<T extends Entity> {
|
|
9
|
+
|
|
10
|
+
value<K extends keyof T>(entity: T, key: string): T[K] | T[K][] | string | number | undefined;
|
|
11
|
+
|
|
12
|
+
set<K extends keyof T>(entity: T, key: T[K]): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ColumnField<T extends Entity> implements EntityField<T> {
|
|
16
|
+
constructor(
|
|
17
|
+
public entity: EntityConstructor<T>,
|
|
18
|
+
public property: string,
|
|
19
|
+
public type: () => Function,
|
|
20
|
+
public column: string
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
value<K extends keyof T>(entity: T, key: string): T[K] | T[K][] | string | number | undefined {
|
|
24
|
+
return entity[key as K]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set(target: T, value: T[keyof T]) {
|
|
28
|
+
target[this.property as keyof T] = value
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class JSONField<T extends Entity> extends ColumnField<T> {
|
|
33
|
+
|
|
34
|
+
value<K extends keyof T>(entity: T, key: string): T[K] | T[K][] | string | number | undefined {
|
|
35
|
+
return JSON.stringify(entity[key as K])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set(target: T, value: T[keyof T]) {
|
|
39
|
+
target[this.property as keyof T] = typeof value === 'string' ? JSON.parse(value) : undefined
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export abstract class InverseField<T extends Entity> implements EntityField<T> {
|
|
44
|
+
protected constructor(
|
|
45
|
+
public entity: EntityConstructor<T>,
|
|
46
|
+
public property: string,
|
|
47
|
+
public type: () => Function
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
value<K extends keyof T>(entity: T, key: string): T[K] | T[K][] | string | number | undefined {
|
|
51
|
+
return entity[key as K]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
abstract set(target: T, value: T[keyof T]): void
|
|
55
|
+
|
|
56
|
+
// relationshipColumnValue<K extends keyof T>(entity: T, value: T[K]): T[K] | T[K][] | string | number | undefined {
|
|
57
|
+
// return value;
|
|
58
|
+
// }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class PrimaryField<T extends Entity> extends ColumnField<T> {
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export abstract class RelationshipField<T extends Entity, R extends Entity> extends ColumnField<T> {
|
|
65
|
+
abstract relationClass: typeof BaseRelationship
|
|
66
|
+
|
|
67
|
+
protected constructor(
|
|
68
|
+
entity: EntityConstructor<T>,
|
|
69
|
+
property: string,
|
|
70
|
+
public type: () => EntityConstructor<R>,
|
|
71
|
+
public inverseBy: string,
|
|
72
|
+
column: string
|
|
73
|
+
) {
|
|
74
|
+
super(entity, property, type, column)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get related() {
|
|
78
|
+
return this.type()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
abstract match(entities: T[], relatedEntities: R[]): T[]
|
|
82
|
+
|
|
83
|
+
abstract matchCounts(entities: T[], counts: T[]): T[]
|
|
84
|
+
|
|
85
|
+
abstract getRelatedValue(
|
|
86
|
+
relatedEntities: R[],
|
|
87
|
+
eagerLoad: EntityQuery<T>['eagerLoad'],
|
|
88
|
+
customQuery?: (query: Query) => void
|
|
89
|
+
): Promise<R[]>
|
|
90
|
+
|
|
91
|
+
abstract getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void): Promise<T[]>
|
|
92
|
+
|
|
93
|
+
abstract update(target: T): void
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export abstract class InverseRelationship<T extends Entity, R extends Entity> extends InverseField<T> {
|
|
97
|
+
abstract relationClass: typeof BaseRelationship
|
|
98
|
+
|
|
99
|
+
protected constructor(
|
|
100
|
+
entity: EntityConstructor<T>,
|
|
101
|
+
property: string,
|
|
102
|
+
public type: () => EntityConstructor<R>,
|
|
103
|
+
public inverseBy: string
|
|
104
|
+
) {
|
|
105
|
+
super(entity, property, type)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get related() {
|
|
109
|
+
return this.type()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
abstract match(entities: T[], relatedEntities: R[]): T[]
|
|
113
|
+
|
|
114
|
+
abstract matchCounts(entities: T[], counts: T[]): T[]
|
|
115
|
+
|
|
116
|
+
abstract getRelatedValue(
|
|
117
|
+
relatedEntities: R[],
|
|
118
|
+
eagerLoad: EntityQuery<T>['eagerLoad'],
|
|
119
|
+
customQuery?: (query: Query) => void
|
|
120
|
+
): Promise<R[]>
|
|
121
|
+
|
|
122
|
+
abstract getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void): Promise<T[]>
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class HasOneField<T extends Entity, R extends Entity> extends InverseRelationship<T, R> {
|
|
127
|
+
relationClass = HasOne
|
|
128
|
+
|
|
129
|
+
constructor(
|
|
130
|
+
entity: EntityConstructor<T>,
|
|
131
|
+
property: string,
|
|
132
|
+
type: () => EntityConstructor<R>,
|
|
133
|
+
inverseBy: string // TODO make it key of R
|
|
134
|
+
) {
|
|
135
|
+
super(entity, property, type, inverseBy)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
set(target: T, value: T[keyof T]) {
|
|
139
|
+
(target[this.property as keyof T] as InstanceType<this['relationClass']>).set(value as unknown as R)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getRelatedValue(relatedEntities: R[], eagerLoad: EntityQuery<T>['eagerLoad'], customQuery?: (query: Query) => void) {
|
|
143
|
+
const parentIds = relatedEntities.pluck(this.entity.getPrimaryKey()) as number[]
|
|
144
|
+
const inverseByField = this.related.metadata.relationships[this.inverseBy]
|
|
145
|
+
const query = this.related.with(...eagerLoad).whereIn(inverseByField.column as EntityKeys<R>, parentIds)
|
|
146
|
+
|
|
147
|
+
customQuery?.(query)
|
|
148
|
+
|
|
149
|
+
return await query.get()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void) {
|
|
153
|
+
const relatedField = this.related.metadata.relationships[this.inverseBy]
|
|
154
|
+
|
|
155
|
+
const query = this.related.newQuery()
|
|
156
|
+
.whereIn(relatedField.column, relatedEntities.pluck(this.entity.getPrimaryKey()))
|
|
157
|
+
.select(relatedField.column)
|
|
158
|
+
.groupBy(relatedField.column)
|
|
159
|
+
|
|
160
|
+
customQuery?.(query)
|
|
161
|
+
|
|
162
|
+
return await query
|
|
163
|
+
.selectCount()
|
|
164
|
+
.get() as unknown as T[]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
match(entities: T[], relatedEntities: R[]): T[] {
|
|
168
|
+
entities.forEach(entity => {
|
|
169
|
+
const inverseField = this.related.metadata.relationships[this.inverseBy]
|
|
170
|
+
const relatedEntity = relatedEntities.find(
|
|
171
|
+
related => related.original[inverseField.column as keyof object] === entity[this.entity.getPrimaryKey()]
|
|
172
|
+
);
|
|
173
|
+
// TODO fix these weird types. Get rid of `unknown`
|
|
174
|
+
(entity[this.property as keyof T] as unknown as BelongsTo<R>).set(relatedEntity as R)
|
|
175
|
+
})
|
|
176
|
+
return entities
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
matchCounts(entities: T[], counts: T[]): T[] {
|
|
180
|
+
const relatedField = this.related.metadata.relationships[this.inverseBy]
|
|
181
|
+
entities.forEach(entity => {
|
|
182
|
+
const count = counts.find(item => item.original[relatedField.column] === entity[this.entity.getPrimaryKey()])
|
|
183
|
+
entity[this.property + 'Count' as keyof T] = (count?.original.aggregate ?? 0) as unknown as T[keyof T]
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
return entities
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export class HasManyField<T extends Entity, R extends Entity> extends InverseRelationship<T, R> {
|
|
191
|
+
relationClass = HasMany
|
|
192
|
+
|
|
193
|
+
constructor(entity: EntityConstructor<T>, property: string, type: () => EntityConstructor<R>, public inverseBy: string) {
|
|
194
|
+
super(entity, property, type, inverseBy)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
set(target: T, value: T[keyof T]) {
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getRelatedValue(relatedEntities: R[], eagerLoad: EntityQuery<T>['eagerLoad'], customQuery?: (query: Query) => void) {
|
|
202
|
+
const parentIds = relatedEntities.pluck(this.entity.getPrimaryKey()) as number[]
|
|
203
|
+
const inverseByField = this.related.metadata.relationships[this.inverseBy]
|
|
204
|
+
const query = this.related.with(...eagerLoad).whereIn(inverseByField.column as EntityKeys<R>, parentIds)
|
|
205
|
+
|
|
206
|
+
customQuery?.(query)
|
|
207
|
+
|
|
208
|
+
return await query.get()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void) {
|
|
212
|
+
const relatedField = this.related.metadata.relationships[this.inverseBy]
|
|
213
|
+
|
|
214
|
+
const query = this.related.newQuery()
|
|
215
|
+
.whereIn(relatedField.column, relatedEntities.pluck(this.entity.getPrimaryKey()))
|
|
216
|
+
.select(relatedField.column)
|
|
217
|
+
.groupBy(relatedField.column)
|
|
218
|
+
|
|
219
|
+
customQuery?.(query)
|
|
220
|
+
|
|
221
|
+
return await query
|
|
222
|
+
.selectCount()
|
|
223
|
+
.get() as unknown as T[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
match(entities: T[], relatedEntities: R[]): T[] {
|
|
227
|
+
entities.forEach(entity => {
|
|
228
|
+
const inverseField = this.related.metadata.relationships[this.inverseBy]
|
|
229
|
+
const relatedEntity = relatedEntities.filter(
|
|
230
|
+
related => related.original[inverseField.column as keyof object] === entity[this.entity.getPrimaryKey()]
|
|
231
|
+
);
|
|
232
|
+
// TODO fix these weird types. Get rid of `unknown`
|
|
233
|
+
(entity[this.property as keyof T] as unknown as HasMany<R, T>).items = relatedEntity
|
|
234
|
+
})
|
|
235
|
+
return entities
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
matchCounts(entities: T[], counts: T[]): T[] {
|
|
239
|
+
const relatedField = this.related.metadata.relationships[this.inverseBy]
|
|
240
|
+
entities.forEach(entity => {
|
|
241
|
+
const count = counts.find(item => item.original[relatedField.column] === entity[this.entity.getPrimaryKey()])
|
|
242
|
+
entity[this.property + 'Count' as keyof T] = (count?.original.aggregate ?? 0) as unknown as T[keyof T]
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
return entities
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async save(items: Partial<EntityObject<R> | R>[], parent: T) {
|
|
249
|
+
const entities: R[] = []
|
|
250
|
+
for await (const item of items) {
|
|
251
|
+
const entity = item instanceof Entity ? item : this.type().new(item)
|
|
252
|
+
|
|
253
|
+
entity.fill({
|
|
254
|
+
[this.inverseBy]: parent
|
|
255
|
+
})
|
|
256
|
+
await entity.save()
|
|
257
|
+
entities.push(entity as R)
|
|
258
|
+
}
|
|
259
|
+
return entities
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getQuery(parent: T) {
|
|
263
|
+
const inverseField = this.related.metadata.relationships[this.inverseBy]
|
|
264
|
+
return this.type().newQuery().where(inverseField.column, parent[parent.getPrimaryKey()] as unknown as R[keyof R])
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export class BelongsToField<T extends Entity, R extends Entity> extends RelationshipField<T, R> {
|
|
269
|
+
relationClass = BelongsTo
|
|
270
|
+
|
|
271
|
+
constructor(
|
|
272
|
+
entity: EntityConstructor<T>,
|
|
273
|
+
property: string,
|
|
274
|
+
type: () => EntityConstructor<R>,
|
|
275
|
+
inverseBy: string,
|
|
276
|
+
column?: string
|
|
277
|
+
) {
|
|
278
|
+
super(entity, property, type, inverseBy, column || property + (entity.getPrimaryKey() as string).capitalize())
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
set(target: T, value: T[keyof T]) {
|
|
282
|
+
(target[this.property as keyof T] as InstanceType<this['relationClass']>).set(value as unknown as R)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
update(target: T) {
|
|
286
|
+
(target[this.property as keyof T] as InstanceType<this['relationClass']>).update()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
value<K extends keyof T>(entity: T, key: string) {
|
|
290
|
+
const field = entity[key as keyof T] as unknown as BelongsTo<R, T>
|
|
291
|
+
const instance = field.get()
|
|
292
|
+
if (instance) {
|
|
293
|
+
return instance[this.related.getPrimaryKey()]
|
|
294
|
+
}
|
|
295
|
+
// return new this.related({
|
|
296
|
+
// [this.related.getPrimaryKey()]: entity[key as K]
|
|
297
|
+
// }) as unknown as T[K]; // TODO get rid of `unknown`
|
|
298
|
+
// if (key === this.column) {
|
|
299
|
+
// return new this.related({
|
|
300
|
+
// [this.related.getPrimaryKey()]: entity[key as K]
|
|
301
|
+
// }) as unknown as T[K]; // TODO get rid of `unknown`
|
|
302
|
+
// }
|
|
303
|
+
// return entity[this.property as keyof T] as unknown as T[K];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getRelatedValue(parents: R[], eagerLoad: EntityQuery<T>['eagerLoad'], customQuery?: (query: Query) => void) {
|
|
307
|
+
const relationIds = parents
|
|
308
|
+
.map(parent => {
|
|
309
|
+
const relation = (parent[this.property as keyof R] as unknown as BelongsTo<R>).get()
|
|
310
|
+
return relation?.[relation.getPrimaryKey()]
|
|
311
|
+
})
|
|
312
|
+
.filter(Boolean) as unknown as number[]
|
|
313
|
+
if (relationIds.length) {
|
|
314
|
+
const query = this.related.with(...eagerLoad).whereIn(this.related.getPrimaryKey() as EntityKeys<R>, relationIds)
|
|
315
|
+
|
|
316
|
+
if (customQuery) {
|
|
317
|
+
customQuery(query)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return await query.get()
|
|
321
|
+
}
|
|
322
|
+
return []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void) {
|
|
326
|
+
return []
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
match(entities: T[], relatedEntities: R[]): T[] {
|
|
330
|
+
return entities.map(entity => {
|
|
331
|
+
// TODO fix these weird types. Get rid of `unknown`
|
|
332
|
+
const related = (entity[this.property as keyof T] as unknown as BelongsTo<R>).get()
|
|
333
|
+
const value = related?.[related.getPrimaryKey()]
|
|
334
|
+
if (value) {
|
|
335
|
+
const instance = relatedEntities.findWhere(this.related.getPrimaryKey() as keyof R, value)
|
|
336
|
+
if (instance) {
|
|
337
|
+
this.set(entity, instance as unknown as T[keyof T])
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return entity
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
matchCounts(entities: T[], counts: T[]): T[] {
|
|
345
|
+
return entities
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
getQuery(parent: T) {
|
|
349
|
+
return this.entity.newQuery().where(this.column, parent[parent.getPrimaryKey()] as unknown as T[keyof T])
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async save(items: Partial<EntityObject<R> | R>[], parent: T) {
|
|
353
|
+
const entities: R[] = []
|
|
354
|
+
for await (const item of items) {
|
|
355
|
+
const entity = item instanceof Entity ? item : this.related.new(item)
|
|
356
|
+
|
|
357
|
+
entity.fill({
|
|
358
|
+
[this.property]: parent
|
|
359
|
+
})
|
|
360
|
+
await entity.save()
|
|
361
|
+
entities.push(entity as R)
|
|
362
|
+
}
|
|
363
|
+
return entities
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export class BelongsToManyField<T extends Entity, R extends Entity> extends InverseRelationship<T, R> {
|
|
368
|
+
relationClass = BelongsToMany
|
|
369
|
+
|
|
370
|
+
constructor(
|
|
371
|
+
entity: EntityConstructor<T>,
|
|
372
|
+
property: string,
|
|
373
|
+
type: () => EntityConstructor<R>,
|
|
374
|
+
inverseBy: string,
|
|
375
|
+
public table?: string,
|
|
376
|
+
public parentColumn?: string,
|
|
377
|
+
public relatedColumn?: string
|
|
378
|
+
) {
|
|
379
|
+
super(entity, property, type, inverseBy)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
set(target: T, value: T[keyof T]) {
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async getRelatedValue(parents: R[], eagerLoad: EntityQuery<T>['eagerLoad'], customQuery?: (query: Query) => void) {
|
|
386
|
+
const parentIds = parents.pluck(this.entity.getPrimaryKey()) as number[]
|
|
387
|
+
const relatedForeignKey = `${wrap(this.getPivotTable())}.${this.getRelatedForeignKey()}`
|
|
388
|
+
const parentForeignKey = `${wrap(this.getPivotTable())}.${this.getParentForeignKey()}`
|
|
389
|
+
const relatedKey = `${wrap(this.related.getTable())}.${this.related.getPrimaryKey()}`
|
|
390
|
+
|
|
391
|
+
const query = this.related
|
|
392
|
+
.with(...eagerLoad)
|
|
393
|
+
.addSelect(
|
|
394
|
+
new StringExpression(`${wrap(this.related.getTable())}.*`),
|
|
395
|
+
new StringExpression(relatedForeignKey),
|
|
396
|
+
new StringExpression(parentForeignKey)
|
|
397
|
+
)
|
|
398
|
+
.join(this.getPivotTable(), relatedForeignKey, '=', relatedKey)
|
|
399
|
+
.whereIn(parentForeignKey, parentIds.unique())
|
|
400
|
+
|
|
401
|
+
customQuery?.(query)
|
|
402
|
+
|
|
403
|
+
const results = await query.get()
|
|
404
|
+
|
|
405
|
+
return results.map(entity => {
|
|
406
|
+
entity.original.pivot = {
|
|
407
|
+
[this.getParentForeignKey()]: entity.original[this.getParentForeignKey()],
|
|
408
|
+
[this.getRelatedForeignKey()]: entity.original[this.getRelatedForeignKey()],
|
|
409
|
+
}
|
|
410
|
+
delete entity.original[this.getParentForeignKey()]
|
|
411
|
+
delete entity.original[this.getRelatedForeignKey()]
|
|
412
|
+
|
|
413
|
+
return entity
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
getParentForeignKey() {
|
|
418
|
+
return this.parentColumn || `${this.entity.name.toLowerCase()}${(this.entity.getPrimaryKey() as string).capitalize()}`
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
getRelatedForeignKey() {
|
|
422
|
+
return this.relatedColumn || `${this.related.name.toLowerCase()}${(this.related.getPrimaryKey() as string).capitalize()}`
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async getRelatedCount(relatedEntities: R[], customQuery?: (query: Query) => void) {
|
|
426
|
+
const query = Query.table(this.getPivotTable())
|
|
427
|
+
.select(this.getParentForeignKey())
|
|
428
|
+
.groupBy(this.getParentForeignKey())
|
|
429
|
+
.whereIn(this.getParentForeignKey(), relatedEntities.pluck(this.related.getPrimaryKey()) as unknown as number[])
|
|
430
|
+
|
|
431
|
+
customQuery?.(query)
|
|
432
|
+
|
|
433
|
+
return await query.selectCount().get() as unknown as T[]
|
|
434
|
+
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
match(entities: T[], relatedEntities: R[]): T[] {
|
|
438
|
+
return entities.map(entity => {
|
|
439
|
+
const entityPrimaryKey = entity[this.entity.getPrimaryKey()];
|
|
440
|
+
(entity[this.property as keyof T] as unknown as BelongsToMany<R>).items = relatedEntities
|
|
441
|
+
.filter(related => related.original.pivot[this.getParentForeignKey()] === entityPrimaryKey)
|
|
442
|
+
return entity
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
matchCounts(entities: T[], counts: T[]): T[] {
|
|
447
|
+
entities.forEach(entity => {
|
|
448
|
+
const foreignKey = this.getParentForeignKey()
|
|
449
|
+
const count = counts.find(item =>
|
|
450
|
+
(item?.original?.[foreignKey] || item[foreignKey as keyof T]) === entity[this.entity.getPrimaryKey()]
|
|
451
|
+
)
|
|
452
|
+
// @ts-ignore
|
|
453
|
+
const aggregateValue = count?.aggregate || count?.original.aggregate
|
|
454
|
+
entity[this.property + 'Count' as keyof T] = (aggregateValue ?? 0) as unknown as T[keyof T]
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
return entities
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
getPivotTable() {
|
|
461
|
+
return this.table || [this.entity.getTable(), this.related.getTable()].sort().join('_')
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
value<K extends keyof T>(entity: T, key: string): T[K] {
|
|
465
|
+
const value = entity[key as K]
|
|
466
|
+
if (value instanceof Array) {
|
|
467
|
+
return value.map(related => {
|
|
468
|
+
if (related instanceof Entity) {
|
|
469
|
+
return related
|
|
470
|
+
}
|
|
471
|
+
const instance = this.related.new({[this.related.getPrimaryKey()]: related})
|
|
472
|
+
return instance
|
|
473
|
+
}) as unknown as T[K]
|
|
474
|
+
}
|
|
475
|
+
return value
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async add(items: (R | ID)[], parent: T) {
|
|
479
|
+
const entities: R[] = []
|
|
480
|
+
// tslint:disable-next-line:no-any
|
|
481
|
+
const dataToInsert: Record<string, any>[] = []
|
|
482
|
+
for await (const item of items) {
|
|
483
|
+
dataToInsert.push({
|
|
484
|
+
[this.getRelatedForeignKey()]: item instanceof Entity ? item.getPrimaryKeyValue() : item,
|
|
485
|
+
[this.getParentForeignKey()]: parent.getPrimaryKeyValue(),
|
|
486
|
+
})
|
|
487
|
+
entities.push(this.related.new({[this.related.getPrimaryKey()]: item}))
|
|
488
|
+
}
|
|
489
|
+
if (dataToInsert.length) {
|
|
490
|
+
await Query.table(this.getPivotTable()).insert(dataToInsert)
|
|
491
|
+
}
|
|
492
|
+
return entities
|
|
493
|
+
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
getQuery(parent: T) {
|
|
497
|
+
return this.related.newQuery().whereIn(this.related.getPrimaryKey(), query => {
|
|
498
|
+
query.select(this.getRelatedForeignKey())
|
|
499
|
+
.table(this.getPivotTable())
|
|
500
|
+
.where(this.getParentForeignKey(), parent[parent.getPrimaryKey()] as unknown as string)
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export interface EntityFieldOptions {
|
|
506
|
+
table?: string;
|
|
507
|
+
column?: string;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface BelongsToManyFieldOptions extends EntityFieldOptions {
|
|
511
|
+
foreignColumn?: string;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export class BelongsTo<T extends Entity, P extends Entity = Entity> extends BaseRelationship<T, P> {
|
|
515
|
+
|
|
516
|
+
private instance?: T
|
|
517
|
+
|
|
518
|
+
constructor(
|
|
519
|
+
relationship: RelationshipField<P, T>,
|
|
520
|
+
parent: P
|
|
521
|
+
) {
|
|
522
|
+
super(relationship, parent)
|
|
523
|
+
this.update()
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
update() {
|
|
527
|
+
const parentId = this.parent.original[(this.relationship as RelationshipField<P, T>).column]
|
|
528
|
+
if (parentId && !this.instance) {
|
|
529
|
+
this.setById(parentId)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
static relationship<T extends Entity, R extends Entity>(
|
|
534
|
+
metadata: EntityMetadata<T, R>,
|
|
535
|
+
entityClass: EntityConstructor<T>,
|
|
536
|
+
property: keyof T,
|
|
537
|
+
type: () => EntityConstructor<R>,
|
|
538
|
+
inverseBy: keyof R,
|
|
539
|
+
options?: EntityFieldOptions
|
|
540
|
+
) {
|
|
541
|
+
|
|
542
|
+
metadata.relationships[property as string] = new BelongsToField(
|
|
543
|
+
entityClass,
|
|
544
|
+
property as string,
|
|
545
|
+
type,
|
|
546
|
+
inverseBy as string,
|
|
547
|
+
options?.column,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
return metadata
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
get() {
|
|
554
|
+
return this.instance
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
set(instance: T | number) {
|
|
558
|
+
if (instance instanceof Entity) {
|
|
559
|
+
this.instance = instance
|
|
560
|
+
} else {
|
|
561
|
+
this.setById(instance)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
setById(id: number) {
|
|
566
|
+
this.set(this.relationship.related.new({[this.relationship.related.getPrimaryKey()]: id}))
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async save(data: EntityObject<T> | {} | undefined = {}): Promise<T | undefined> {
|
|
570
|
+
if (!this.instance) {
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
this.instance.fill({
|
|
574
|
+
...data,
|
|
575
|
+
// [this.relationship.column]: this.parent
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
await this.instance.save()
|
|
579
|
+
return this.instance
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
toJSON() {
|
|
583
|
+
return this.instance?.toJSON()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export class HasOne<T extends Entity, P extends Entity = Entity> extends BaseRelationship<T, P> {
|
|
589
|
+
|
|
590
|
+
declare public relationship: RelationshipField<P, T>
|
|
591
|
+
private instance?: T
|
|
592
|
+
|
|
593
|
+
static relationship<T extends Entity, R extends Entity>(
|
|
594
|
+
metadata: EntityMetadata<T, R>,
|
|
595
|
+
entityClass: EntityConstructor<T>,
|
|
596
|
+
property: keyof T,
|
|
597
|
+
type: () => EntityConstructor<R>,
|
|
598
|
+
inverseBy: keyof R,
|
|
599
|
+
options?: EntityFieldOptions
|
|
600
|
+
) {
|
|
601
|
+
metadata.inverseRelationships[property as string] = new HasOneField(
|
|
602
|
+
entityClass,
|
|
603
|
+
property as string,
|
|
604
|
+
type,
|
|
605
|
+
inverseBy as string,
|
|
606
|
+
)
|
|
607
|
+
return metadata
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
get() {
|
|
611
|
+
return this.instance
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
set(instance: T | number | undefined) {
|
|
615
|
+
if (!instance) {
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
if (instance instanceof Entity) {
|
|
619
|
+
this.instance = instance
|
|
620
|
+
} else {
|
|
621
|
+
this.setById(instance)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
setById(id: number) {
|
|
626
|
+
this.set(this.relationship.related.new({[this.relationship.related.getPrimaryKey()]: id}))
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async save(instance: T | undefined = this.instance): Promise<T | undefined> {
|
|
630
|
+
this.set(instance)
|
|
631
|
+
|
|
632
|
+
if (instance) {
|
|
633
|
+
(instance[this.relationship.inverseBy as keyof T] as unknown as BelongsTo<P, T>).set(this.parent)
|
|
634
|
+
await instance.save()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return this.instance
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
toJSON() {
|
|
641
|
+
return this.instance?.toJSON()
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export class HasMany<T extends Entity, P extends Entity = Entity> extends List<T, P> {
|
|
646
|
+
|
|
647
|
+
constructor(
|
|
648
|
+
public relationship: HasManyField<P, T>,
|
|
649
|
+
parent: P
|
|
650
|
+
) {
|
|
651
|
+
super(relationship, parent)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
static relationship<T extends Entity, R extends Entity>(
|
|
655
|
+
metadata: EntityMetadata<T, R>,
|
|
656
|
+
entityClass: EntityConstructor<T>,
|
|
657
|
+
property: keyof T,
|
|
658
|
+
type: () => EntityConstructor<R>,
|
|
659
|
+
inverseBy: keyof R,
|
|
660
|
+
options?: EntityFieldOptions
|
|
661
|
+
) {
|
|
662
|
+
metadata.inverseRelationships[property as string] = new HasManyField<T, R>(
|
|
663
|
+
entityClass,
|
|
664
|
+
property as string,
|
|
665
|
+
type,
|
|
666
|
+
inverseBy as string,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
return metadata
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async save(item: Partial<EntityObject<T> | T>): Promise<T> {
|
|
673
|
+
const instances = await this.saveMany(item)
|
|
674
|
+
return instances.first() as T
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async saveMany(...items: Partial<EntityObject<T> | T>[]): Promise<T[]> {
|
|
678
|
+
if (!this.parent.exists) {
|
|
679
|
+
await this.parent.save()
|
|
680
|
+
}
|
|
681
|
+
const instances = await this.relationship.save(items, this.parent)
|
|
682
|
+
|
|
683
|
+
this.items.push(...instances)
|
|
684
|
+
|
|
685
|
+
return instances
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async clear() {
|
|
689
|
+
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export class BelongsToMany<T extends Entity, P extends Entity = Entity> extends List<T, P> {
|
|
695
|
+
|
|
696
|
+
constructor(
|
|
697
|
+
public relationship: BelongsToManyField<P, T>,
|
|
698
|
+
parent: P
|
|
699
|
+
) {
|
|
700
|
+
super(relationship, parent)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
static relationship<T extends Entity, R extends Entity>(
|
|
704
|
+
metadata: EntityMetadata<T, R>,
|
|
705
|
+
entityClass: EntityConstructor<T>,
|
|
706
|
+
property: keyof T,
|
|
707
|
+
type: () => EntityConstructor<R>,
|
|
708
|
+
inverseBy: keyof R,
|
|
709
|
+
options?: BelongsToManyFieldOptions
|
|
710
|
+
) {
|
|
711
|
+
// options.column = options.column || (property as string) + (entityClass.getPrimaryKey() as string).capitalize();
|
|
712
|
+
|
|
713
|
+
const inverseField = metadata.inverseRelationships[inverseBy as string] as BelongsToManyField<T, R> | undefined
|
|
714
|
+
|
|
715
|
+
metadata.inverseRelationships[property as string] = new BelongsToManyField(
|
|
716
|
+
entityClass,
|
|
717
|
+
property as string,
|
|
718
|
+
type,
|
|
719
|
+
inverseBy as string,
|
|
720
|
+
options?.table || inverseField?.table,
|
|
721
|
+
options?.foreignColumn || inverseField?.parentColumn,
|
|
722
|
+
options?.column || inverseField?.relatedColumn,
|
|
723
|
+
)
|
|
724
|
+
return metadata
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async has(item: T | ID) {
|
|
728
|
+
if (item instanceof Entity) {
|
|
729
|
+
item = item.getPrimaryKeyValue()
|
|
730
|
+
}
|
|
731
|
+
const relatedForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getRelatedForeignKey()}`
|
|
732
|
+
const parentForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getParentForeignKey()}`
|
|
733
|
+
|
|
734
|
+
return Boolean(
|
|
735
|
+
await Query.table(this.relationship.getPivotTable())
|
|
736
|
+
.where(parentForeignKey, this.parent[this.parent.getPrimaryKey()] as unknown as string)
|
|
737
|
+
.andWhere(relatedForeignKey, item)
|
|
738
|
+
.first()
|
|
739
|
+
)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async add(...items: (T | ID)[]) {
|
|
743
|
+
const ids = items.map(item => item instanceof Entity ? item.getPrimaryKeyValue() : item)
|
|
744
|
+
if (!this.parent.exists) {
|
|
745
|
+
await this.parent.save()
|
|
746
|
+
}
|
|
747
|
+
for await (const id of ids) {
|
|
748
|
+
if (!(await this.has(id))) {
|
|
749
|
+
this.items = this.items.concat(await this.relationship.add(items, this.parent))
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return this.items
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async remove(...items: (T | ID)[]) {
|
|
757
|
+
const ids = items.map(item => item instanceof Entity ? item.getPrimaryKeyValue() : item)
|
|
758
|
+
|
|
759
|
+
const relatedForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getRelatedForeignKey()}`
|
|
760
|
+
const parentForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getParentForeignKey()}`
|
|
761
|
+
|
|
762
|
+
await Query.table(this.relationship.getPivotTable())
|
|
763
|
+
.where(parentForeignKey, this.parent[this.parent.getPrimaryKey()] as unknown as string)
|
|
764
|
+
.whereIn(relatedForeignKey, ids)
|
|
765
|
+
.delete()
|
|
766
|
+
|
|
767
|
+
this.items = this.items.filter(item => !ids.includes(item.getPrimaryKeyValue()))
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async toggle(...items: (T | ID)[]) {
|
|
771
|
+
const ids = items.map(item => item instanceof Entity ? item.getPrimaryKeyValue() : item)
|
|
772
|
+
|
|
773
|
+
const attachedEntities = await this.relationship.getQuery(this.parent).get()
|
|
774
|
+
const entitiesToDetach = ids.filter(item =>
|
|
775
|
+
attachedEntities.some(entity => entity.getPrimaryKeyValue() === item)
|
|
776
|
+
)
|
|
777
|
+
const entitiesToAttach = ids.filter(item =>
|
|
778
|
+
attachedEntities.some(entity => entity.getPrimaryKeyValue() as unknown as number !== item)
|
|
779
|
+
)
|
|
780
|
+
await this.add(...entitiesToAttach)
|
|
781
|
+
await this.remove(...entitiesToDetach)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async clear() {
|
|
785
|
+
const relatedForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getRelatedForeignKey()}`
|
|
786
|
+
const parentForeignKey = `${this.relationship.getPivotTable()}.${this.relationship.getParentForeignKey()}`
|
|
787
|
+
|
|
788
|
+
await Query.table(this.relationship.getPivotTable())
|
|
789
|
+
.where(parentForeignKey, this.parent[this.parent.getPrimaryKey()] as unknown as string)
|
|
790
|
+
.whereIn(relatedForeignKey, this.items.pluck(this.relationship.related.getPrimaryKey()))
|
|
791
|
+
.delete()
|
|
792
|
+
|
|
793
|
+
this.items = []
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async sync(...items: (T | ID)[]) {
|
|
797
|
+
const ids = items.map(item => item instanceof Entity ? item.getPrimaryKeyValue() : item)
|
|
798
|
+
|
|
799
|
+
const existingItems = await this.get()
|
|
800
|
+
const primaryKey = this.relationship.type().getPrimaryKey()
|
|
801
|
+
const itemsToRemove = existingItems.filter(item => !ids.includes(item[primaryKey])).pluck(primaryKey)
|
|
802
|
+
const restOfItemsToAdd = ids.filter(item => !existingItems.findWhere(primaryKey, item as unknown as T[keyof T]))
|
|
803
|
+
await this.remove(...itemsToRemove)
|
|
804
|
+
await this.add(...restOfItemsToAdd)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async syncWithoutDetaching(...items: (T | ID)[]) {
|
|
808
|
+
const ids = items.map(item => item instanceof Entity ? item.getPrimaryKeyValue() : item)
|
|
809
|
+
|
|
810
|
+
const existingItems = await this.get()
|
|
811
|
+
const primaryKey = this.relationship.type().getPrimaryKey()
|
|
812
|
+
const restOfItemsToAdd = ids.filter(item => !existingItems.findWhere(primaryKey, item as unknown as T[keyof T]))
|
|
813
|
+
await this.add(...restOfItemsToAdd)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
}
|