@typetron/framework 0.5.3 → 0.5.4
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,144 @@
|
|
|
1
|
+
import { Database } from 'sqlite3'
|
|
2
|
+
import { SqlValue } from '../../Types'
|
|
3
|
+
import { DatabaseDriver } from '../DatabaseDriver'
|
|
4
|
+
import { Select } from '../SQL/Statements/Select'
|
|
5
|
+
import { Insert } from '../SQL/Statements/Insert'
|
|
6
|
+
import { Delete } from '../SQL/Statements/Delete'
|
|
7
|
+
import { Update } from '../SQL/Statements/Update'
|
|
8
|
+
import { Alter } from '../SQL/Statements/Alter'
|
|
9
|
+
import { Schema } from './Schema'
|
|
10
|
+
import { SchemaContract } from '../SchemaContract'
|
|
11
|
+
import { wrap } from '../../Helpers'
|
|
12
|
+
import { Create } from './Create'
|
|
13
|
+
|
|
14
|
+
interface ColumnInfo {
|
|
15
|
+
name: string
|
|
16
|
+
type: string
|
|
17
|
+
notnull: string
|
|
18
|
+
dflt_value: string
|
|
19
|
+
pk: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SqliteDriver implements DatabaseDriver {
|
|
23
|
+
database: Database
|
|
24
|
+
|
|
25
|
+
schema: SchemaContract = new Schema(this)
|
|
26
|
+
|
|
27
|
+
statements = {
|
|
28
|
+
create: Create,
|
|
29
|
+
select: Select,
|
|
30
|
+
insert: Insert,
|
|
31
|
+
delete: Delete,
|
|
32
|
+
update: Update,
|
|
33
|
+
alter: Alter,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(databaseFilePath: string) {
|
|
37
|
+
this.database = new Database(databaseFilePath)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async run(query: string, params: SqlValue[] = []): Promise<void> {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const appError = new Error()
|
|
43
|
+
this.database.run(query, params, (error) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
return reject(this.buildError(appError, error, query))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
resolve()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async insertOne(query: string, params: SqlValue[] = []): Promise<number> {
|
|
54
|
+
await this.run(query, params)
|
|
55
|
+
return this.lastInsertedId()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async truncate(table: string) {
|
|
59
|
+
await this.run(`DELETE
|
|
60
|
+
FROM ${table}`)
|
|
61
|
+
return this.run(`DELETE
|
|
62
|
+
FROM SQLITE_SEQUENCE
|
|
63
|
+
WHERE name = '${table}'`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async lastInsertedId(): Promise<number> {
|
|
67
|
+
return new Promise<number>((resolve, reject) => {
|
|
68
|
+
const appError = new Error()
|
|
69
|
+
const query = 'SELECT last_insert_rowid() as id;'
|
|
70
|
+
this.database.get(query, [], (error, lastInsert: any) => {
|
|
71
|
+
if (error) {
|
|
72
|
+
return reject(this.buildError(appError, error, query))
|
|
73
|
+
}
|
|
74
|
+
resolve(lastInsert.id)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async get<T>(query: string, params: SqlValue[] = []): Promise<T[]> {
|
|
80
|
+
return new Promise<T[]>((resolve, reject) => {
|
|
81
|
+
const appError = new Error()
|
|
82
|
+
this.database.all(query, params, (error, rows: any) => {
|
|
83
|
+
if (error) {
|
|
84
|
+
return reject(this.buildError(appError, error, query))
|
|
85
|
+
}
|
|
86
|
+
resolve(rows)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async first<T>(query: string, params: SqlValue[] = []): Promise<T> {
|
|
92
|
+
return new Promise<T>((resolve, reject) => {
|
|
93
|
+
const appError = new Error()
|
|
94
|
+
this.database.get(query, params, (error, row: any) => {
|
|
95
|
+
if (error) {
|
|
96
|
+
return reject(this.buildError(appError, error, query))
|
|
97
|
+
}
|
|
98
|
+
resolve(row)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
buildError(appError: Error, databaseError: Error, query?: string) {
|
|
104
|
+
appError.message = `${databaseError} in '${query}' `
|
|
105
|
+
// appError.stack = appError.stack?.replace('Error:', `${appError.message}`)
|
|
106
|
+
return appError
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
tables() {
|
|
110
|
+
return this.get<{name: string}>(
|
|
111
|
+
`SELECT name
|
|
112
|
+
FROM sqlite_master
|
|
113
|
+
WHERE type = 'table'`
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
tableExists(table: string): Promise<boolean> {
|
|
118
|
+
return this.first(
|
|
119
|
+
`SELECT *
|
|
120
|
+
FROM sqlite_master
|
|
121
|
+
WHERE name = ?`,
|
|
122
|
+
[table]
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async tableColumns(table: string) {
|
|
127
|
+
const columnsInfo = await this.get<ColumnInfo>(
|
|
128
|
+
`PRAGMA table_info(${wrap(table)})`
|
|
129
|
+
)
|
|
130
|
+
const autoIncrements = await this.get<{name: string; seq: number}>(`
|
|
131
|
+
SELECT *
|
|
132
|
+
FROM sqlite_sequence
|
|
133
|
+
`)
|
|
134
|
+
|
|
135
|
+
return columnsInfo.map((info) => ({
|
|
136
|
+
name: info.name,
|
|
137
|
+
type: info.type,
|
|
138
|
+
nullable: !info.notnull,
|
|
139
|
+
default: info.dflt_value,
|
|
140
|
+
autoIncrement: Number(Boolean(autoIncrements.findWhere('name', table))),
|
|
141
|
+
primaryKey: Boolean(info.pk),
|
|
142
|
+
}))
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Entity } from '../../Entity'
|
|
2
|
+
import { JSONField, PrimaryField } from '../../Fields'
|
|
3
|
+
|
|
4
|
+
export * from './SqliteDriver'
|
|
5
|
+
|
|
6
|
+
// tslint:disable-next-line:no-any
|
|
7
|
+
export const typesMatches = new Map<any, string>([
|
|
8
|
+
[Entity, 'integer'],
|
|
9
|
+
[PrimaryField, `integer`],
|
|
10
|
+
[Number, 'integer'],
|
|
11
|
+
[String, 'varchar(255)'],
|
|
12
|
+
[Date, 'datetime'],
|
|
13
|
+
[JSONField, 'text'],
|
|
14
|
+
[Boolean, 'integer'],
|
|
15
|
+
])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ColumnField, Entity, EntityMetadata } from '..'
|
|
2
|
+
import { DatabaseDriver } from './DatabaseDriver'
|
|
3
|
+
|
|
4
|
+
export abstract class SchemaContract {
|
|
5
|
+
constructor(public driver: DatabaseDriver) {}
|
|
6
|
+
|
|
7
|
+
abstract create(table: string, columns: ColumnField<Entity>[]): Promise<void>
|
|
8
|
+
|
|
9
|
+
abstract synchronize(entitiesMetadata: EntityMetadata<Entity, Entity>[]): Promise<void>
|
|
10
|
+
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Components, SqlValue } from '../Types'
|
|
2
|
+
import { StringExpression } from '../StringExpression'
|
|
3
|
+
|
|
4
|
+
export abstract class Statement extends StringExpression {
|
|
5
|
+
bindings: SqlValue[] = []
|
|
6
|
+
|
|
7
|
+
constructor(public components: Components) {
|
|
8
|
+
super('')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
abstract toSQL(): string;
|
|
12
|
+
|
|
13
|
+
toString() {
|
|
14
|
+
return this.toSQL()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { EntityProxyHandler } from './EntityProxyHandler'
|
|
2
|
+
import { EntityQuery } from './EntityQuery'
|
|
3
|
+
import { ChildObject, KeysOfType } from '../Support'
|
|
4
|
+
import { EntityMetadata, EntityMetadataKey, ID } from './Decorators'
|
|
5
|
+
import { EntityNotFoundError } from './EntityNotFoundError'
|
|
6
|
+
import { BelongsTo, BelongsToMany, ColumnField, HasMany, HasOne, InverseField } from './Fields'
|
|
7
|
+
import { BooleanOperator, Direction, Operator, WhereFunction, WhereValue } from './Types'
|
|
8
|
+
import { DotNotationProperties, EntityConstructor, EntityKeys, EntityObject } from './index'
|
|
9
|
+
import { Query } from './Query'
|
|
10
|
+
import { List } from './List'
|
|
11
|
+
|
|
12
|
+
export abstract class Entity {
|
|
13
|
+
|
|
14
|
+
exists = false
|
|
15
|
+
// tslint:disable-next-line:no-any
|
|
16
|
+
original: Record<string, any> = {}
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
const entityProxyHandler = new EntityProxyHandler<this>()
|
|
20
|
+
const proxy = new Proxy(this, entityProxyHandler)
|
|
21
|
+
|
|
22
|
+
// Weird JS issues: "filling" an instance with data is not possible since the default values of child entities
|
|
23
|
+
// are assigned after the constructor of base class (this constructor exactly) is called.
|
|
24
|
+
// Calling "proxy.fill will update the instance but then the default values will overwrite them"
|
|
25
|
+
// proxy.fill(this.original = data || {})
|
|
26
|
+
|
|
27
|
+
return proxy
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static get metadata(): EntityMetadata<Entity, Entity> {
|
|
31
|
+
return Reflect.getMetadata(EntityMetadataKey, this.prototype) ?? new EntityMetadata<Entity, Entity>()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get metadata(): EntityMetadata<Entity, Entity> {
|
|
35
|
+
return this.static.metadata
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get static(): EntityConstructor<this> {
|
|
39
|
+
return this.constructor as EntityConstructor<this>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static where<T extends Entity, K extends EntityKeys<T>>(
|
|
43
|
+
this: EntityConstructor<T>,
|
|
44
|
+
column: EntityKeys<T>,
|
|
45
|
+
operator: Operator | Query | WhereValue | T[K],
|
|
46
|
+
value?: Query | WhereValue | T[K],
|
|
47
|
+
boolean?: BooleanOperator
|
|
48
|
+
): EntityQuery<T> {
|
|
49
|
+
const columnMetadata = {
|
|
50
|
+
...this.metadata.columns,
|
|
51
|
+
...this.metadata.relationships
|
|
52
|
+
}[column as string]
|
|
53
|
+
return this.newQuery().where(columnMetadata.column, operator, value, boolean)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static whereIn<T extends Entity, K extends EntityKeys<T>>(
|
|
57
|
+
this: EntityConstructor<T>,
|
|
58
|
+
column: EntityKeys<T>,
|
|
59
|
+
value: Query | WhereValue[] | T[K][],
|
|
60
|
+
boolean?: BooleanOperator
|
|
61
|
+
): EntityQuery<T> {
|
|
62
|
+
const columnMetadata = {
|
|
63
|
+
...this.metadata.columns,
|
|
64
|
+
...this.metadata.relationships
|
|
65
|
+
}[column as string]
|
|
66
|
+
return this.newQuery().whereIn(columnMetadata.column, value, boolean)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static whereNotIn<T extends Entity, K extends EntityKeys<T>>(
|
|
70
|
+
this: EntityConstructor<T>,
|
|
71
|
+
column: EntityKeys<T>,
|
|
72
|
+
value: Query | WhereFunction | WhereValue[] | T[K][]
|
|
73
|
+
): EntityQuery<T> {
|
|
74
|
+
const columnMetadata = {
|
|
75
|
+
...this.metadata.columns,
|
|
76
|
+
...this.metadata.relationships
|
|
77
|
+
}[column as string]
|
|
78
|
+
return this.newQuery().whereNotIn(columnMetadata.column, value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static whereLike<T extends Entity, K extends EntityKeys<T>>(
|
|
82
|
+
this: EntityConstructor<T>,
|
|
83
|
+
column: EntityKeys<T>,
|
|
84
|
+
value?: WhereValue | T[K],
|
|
85
|
+
boolean?: BooleanOperator
|
|
86
|
+
): EntityQuery<T> {
|
|
87
|
+
const columnMetadata = {
|
|
88
|
+
...this.metadata.columns,
|
|
89
|
+
...this.metadata.relationships
|
|
90
|
+
}[column as string]
|
|
91
|
+
return this.newQuery().whereLike(columnMetadata.column, value as WhereValue, boolean)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static async first<T extends Entity, K extends EntityKeys<T>>(
|
|
95
|
+
this: EntityConstructor<T>,
|
|
96
|
+
...columns: EntityKeys<T>[]
|
|
97
|
+
): Promise<T | undefined> {
|
|
98
|
+
return this.newQuery().first(...columns)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// static first2: <T extends Entity>(this: EntityConstructor<T>, ...params: Parameters<Query<T>['first']>) => Query<T>['first'];
|
|
102
|
+
|
|
103
|
+
// tslint:disable-next-line:no-any
|
|
104
|
+
static async firstOrNew<T extends Entity, K extends keyof Entity>(
|
|
105
|
+
this: EntityConstructor<T>,
|
|
106
|
+
properties: Partial<EntityObject<T>>,
|
|
107
|
+
values?: Partial<EntityObject<T>>
|
|
108
|
+
): Promise<T> {
|
|
109
|
+
return this.newQuery().firstOrNew(properties, values)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// tslint:disable-next-line:no-any
|
|
113
|
+
static async firstOrCreate<T extends Entity, K extends keyof Entity>(
|
|
114
|
+
this: EntityConstructor<T>,
|
|
115
|
+
properties: Partial<EntityObject<T>>,
|
|
116
|
+
values?: Partial<EntityObject<T>>
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
return (await this.firstOrNew(properties, values)).save()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static orderBy<T extends Entity, K extends EntityKeys<T>>(
|
|
122
|
+
this: EntityConstructor<T>,
|
|
123
|
+
column: EntityKeys<T>,
|
|
124
|
+
direction?: Direction
|
|
125
|
+
): EntityQuery<T> {
|
|
126
|
+
return this.newQuery().orderBy(column, direction)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static getTable<T extends Entity>(this: EntityConstructor<T>): string {
|
|
130
|
+
return this.metadata.table || this.name.toLowerCase() // TODO make metadata.table un-optional
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static new<T extends Entity, K extends keyof T>(
|
|
134
|
+
this: EntityConstructor<T>,
|
|
135
|
+
data: Partial<ChildObject<T, Entity>> | {},
|
|
136
|
+
exists = false
|
|
137
|
+
): T {
|
|
138
|
+
const instance = new (this as any)()
|
|
139
|
+
instance.fill(instance.original = data)
|
|
140
|
+
instance.exists = exists
|
|
141
|
+
|
|
142
|
+
return instance
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static create<T extends Entity>(this: EntityConstructor<T>, data?: ChildObject<T, Entity> | {}): Promise<T> {
|
|
146
|
+
return this.new(data ?? {}).save()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static async insert<T extends Entity>(this: EntityConstructor<T>, data: (ChildObject<T, Entity> | {})[]): Promise<void> {
|
|
150
|
+
const dataToSave = data.map(piece => {
|
|
151
|
+
const instance = this.new(piece)
|
|
152
|
+
return instance.getDataToSave(piece)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const query = this.newQuery()
|
|
156
|
+
|
|
157
|
+
await query.insert(dataToSave)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async get<T extends Entity>(this: EntityConstructor<T>, ...columns: EntityKeys<T> []): Promise<T[]> {
|
|
161
|
+
return this.newQuery().get(...columns)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static with<T extends Entity>(
|
|
165
|
+
this: EntityConstructor<T>,
|
|
166
|
+
...relations: (
|
|
167
|
+
// tslint:disable-next-line:no-any
|
|
168
|
+
KeysOfType<T, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<T> |
|
|
169
|
+
// tslint:disable-next-line:no-any max-line-length
|
|
170
|
+
[KeysOfType<T, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<T>, (Query: EntityQuery<any>) => void]
|
|
171
|
+
)[]
|
|
172
|
+
) {
|
|
173
|
+
return this.newQuery().with(...relations)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static withCount<T extends Entity, K extends KeysOfType<T, List<Entity>>>(
|
|
177
|
+
this: EntityConstructor<T>,
|
|
178
|
+
...relations: (
|
|
179
|
+
// tslint:disable-next-line:no-any
|
|
180
|
+
KeysOfType<T, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<T> |
|
|
181
|
+
// tslint:disable-next-line:no-any max-line-length
|
|
182
|
+
[KeysOfType<T, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<T>, (Query: EntityQuery<any>) => void]
|
|
183
|
+
)[]
|
|
184
|
+
) {
|
|
185
|
+
return this.newQuery().withCount(...relations)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static async find<T extends Entity>(this: EntityConstructor<T>, id: string | number | ID): Promise<T | undefined> {
|
|
189
|
+
return this.newQuery().where('id' as keyof T, id as number).first()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static async findOrFail<T extends Entity>(this: EntityConstructor<T>, id: string | number | ID): Promise<T> {
|
|
193
|
+
const query = this.newQuery().where('id' as keyof T, id as number)
|
|
194
|
+
const instance = await query.first()
|
|
195
|
+
if (!instance || !Object.entries(instance).length) {
|
|
196
|
+
throw new EntityNotFoundError(this, `No records found for entity '${this.name}' when querying with parameters [${query.getBindings().join(', ')}]`)
|
|
197
|
+
}
|
|
198
|
+
return instance
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static getPrimaryKey<T extends Entity>(): EntityKeys<T> {
|
|
202
|
+
return 'id' as EntityKeys<T>
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static hydrate<T extends Entity>(modelType: EntityConstructor<T>, models: Partial<ChildObject<T, Entity>>[], exist = false) {
|
|
206
|
+
return models.map(data => {
|
|
207
|
+
return modelType.new(data, exist)
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static newQuery<T extends Entity>(this: EntityConstructor<T>): EntityQuery<T> {
|
|
212
|
+
return (new EntityQuery(this)).table(this.getTable()) as EntityQuery<T>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getTable(): string {
|
|
216
|
+
return this.static.getTable()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async load(
|
|
220
|
+
...relations: (
|
|
221
|
+
// tslint:disable-next-line:no-any
|
|
222
|
+
KeysOfType<this, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<this> |
|
|
223
|
+
// tslint:disable-next-line:no-any max-line-length
|
|
224
|
+
[KeysOfType<this, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<this>, (Query: EntityQuery<any>) => void]
|
|
225
|
+
)[]
|
|
226
|
+
) {
|
|
227
|
+
await this.newQuery().with(...relations).eagerLoadRelationships([this])
|
|
228
|
+
|
|
229
|
+
return this
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async loadCount<K extends KeysOfType<this, List<Entity>>>(
|
|
233
|
+
...relations: (
|
|
234
|
+
// tslint:disable-next-line:no-any
|
|
235
|
+
KeysOfType<this, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<this> |
|
|
236
|
+
// tslint:disable-next-line:no-any max-line-length
|
|
237
|
+
[KeysOfType<this, BelongsTo<any> | HasOne<any> | HasMany<any> | BelongsToMany<any>> | DotNotationProperties<this>, (Query: EntityQuery<any>) => void]
|
|
238
|
+
)[]
|
|
239
|
+
) {
|
|
240
|
+
await this.newQuery().withCount<K>(...relations).eagerLoadRelationshipsCounts([this])
|
|
241
|
+
|
|
242
|
+
return this
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
newQuery<T extends Entity>(this: T) {
|
|
246
|
+
return this.static.newQuery()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static async delete<T extends Entity>(this: EntityConstructor<T>, ...ids: [ID, ...ID[]]) {
|
|
250
|
+
await this.newQuery().whereIn(this.getPrimaryKey(), ids).delete()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private getDataToSave(data: ChildObject<this, Entity> | object) {
|
|
254
|
+
this.fill(data)
|
|
255
|
+
|
|
256
|
+
this.updateTimestamps()
|
|
257
|
+
|
|
258
|
+
// tslint:disable-next-line:no-any
|
|
259
|
+
const dataToSave: Record<string, any> = {}
|
|
260
|
+
|
|
261
|
+
// TODO diff the values so we don't update every value from the entity
|
|
262
|
+
Object.values({...this.metadata.columns, ...this.metadata.relationships}).forEach(field => {
|
|
263
|
+
dataToSave[field.column] = field.value(this, field.property)
|
|
264
|
+
})
|
|
265
|
+
return dataToSave
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
fill(data: ChildObject<this, Entity> | object) {
|
|
270
|
+
// TODO, check if we need to uncomment this
|
|
271
|
+
// const relationships = this.metadata.relationships
|
|
272
|
+
// Object.keys(relationships).forEach(key => {
|
|
273
|
+
// relationships[key].update(this)
|
|
274
|
+
// })
|
|
275
|
+
const fields = {...this.metadata.columns, ...this.metadata.relationships, ...this.metadata.inverseRelationships}
|
|
276
|
+
Object.keys(data).forEach(key => {
|
|
277
|
+
const field = fields[key]
|
|
278
|
+
if (field) {
|
|
279
|
+
const value = data[key as keyof object]
|
|
280
|
+
field.set(this, this.convertValueByType(value, field))
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
return this
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async delete() {
|
|
288
|
+
if (!this.exists) {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await this.newQuery().where(this.getPrimaryKey(), this[this.getPrimaryKey()]).delete()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static async truncate<T extends Entity>(this: EntityConstructor<T>) {
|
|
296
|
+
await this.newQuery().truncate()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async save(data: ChildObject<this, Entity> | object = {}): Promise<this> {
|
|
300
|
+
const dataToSave = this.getDataToSave(data)
|
|
301
|
+
|
|
302
|
+
const query = this.newQuery()
|
|
303
|
+
|
|
304
|
+
if (this.exists) {
|
|
305
|
+
delete dataToSave[this.getPrimaryKey() as string]
|
|
306
|
+
await query.where(this.getPrimaryKey(), this[this.getPrimaryKey()]).update(dataToSave)
|
|
307
|
+
} else {
|
|
308
|
+
const id = await query.insertOne(dataToSave)
|
|
309
|
+
this.exists = true
|
|
310
|
+
this.fill({id})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return this
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
getPrimaryKey<T extends Entity>(this: T): EntityKeys<T> {
|
|
317
|
+
return this.static.getPrimaryKey()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
getPrimaryKeyValue<T extends Entity>(this: T): ID {
|
|
321
|
+
return this[this.static.getPrimaryKey()]
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
toJSON() {
|
|
325
|
+
return Object.keys({...this.metadata.columns, ...this.metadata.allRelationships})
|
|
326
|
+
.reduce((obj, key) => {
|
|
327
|
+
if (Object.prototype.hasOwnProperty.call(this, key)) {
|
|
328
|
+
obj[key as keyof this] = this[key as keyof this]
|
|
329
|
+
}
|
|
330
|
+
return obj
|
|
331
|
+
}, {} as { [K in keyof this]: this[K] })
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private updateTimestamps() {
|
|
335
|
+
const metadata = this.metadata
|
|
336
|
+
const columnsToFill: Record<keyof this, unknown> = {} as Record<keyof this, unknown>
|
|
337
|
+
|
|
338
|
+
const createdAtValue = metadata.createdAtColumn ? this[metadata.createdAtColumn as keyof this] as unknown as Date : undefined
|
|
339
|
+
if (metadata.createdAtColumn && !createdAtValue) {
|
|
340
|
+
columnsToFill[metadata.createdAtColumn as keyof this] = new Date()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const updatedAtValue = metadata.updatedAtColumn ? this[metadata.updatedAtColumn as keyof this] as unknown as Date : undefined
|
|
344
|
+
if (metadata.updatedAtColumn &&
|
|
345
|
+
(!updatedAtValue || updatedAtValue?.getTime() === new Date(this.original[metadata.updatedAtColumn]).getTime())
|
|
346
|
+
) {
|
|
347
|
+
columnsToFill[metadata.updatedAtColumn as keyof this] = new Date()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.fill(columnsToFill)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private convertValueByType(value: unknown, property: ColumnField<Entity> | InverseField<Entity>) {
|
|
354
|
+
const converter = types.get(property.type()) || (() => value)
|
|
355
|
+
return converter(value)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const types: Map<Function, Function> = new Map()
|
|
360
|
+
types.set(Date, (value: number | string) => value ? new Date(value) : value)
|
|
361
|
+
types.set(String, (value: object) => value ? String(value) : value)
|
|
362
|
+
types.set(Boolean, (value: string) => Boolean(value))
|
|
363
|
+
|
|
364
|
+
// let handler = {
|
|
365
|
+
// set: (target, prop, value) => {
|
|
366
|
+
// console.log(target, prop, value);
|
|
367
|
+
// target[prop] = value;
|
|
368
|
+
// return true;
|
|
369
|
+
// },
|
|
370
|
+
// get: (target, prop) => {
|
|
371
|
+
// if(!(prop in Entity))
|
|
372
|
+
// return () => {return new Query};
|
|
373
|
+
// return target[prop];
|
|
374
|
+
// }
|
|
375
|
+
// };
|
|
376
|
+
|
|
377
|
+
// Object.setPrototypeOf(Entity, new Proxy({}, new EntityProxyHandler(Entity)));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Entity } from './Entity'
|
|
2
|
+
import { EntityQuery } from './EntityQuery'
|
|
3
|
+
import { EntityConstructor } from './index'
|
|
4
|
+
|
|
5
|
+
export class EntityProxyHandler<T extends Entity> {
|
|
6
|
+
|
|
7
|
+
set<K extends keyof T>(target: T, property: string, value: T[K]) {
|
|
8
|
+
target[property as keyof T] = value
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get(target: T, property: string) {
|
|
13
|
+
if (!(property in target)) {
|
|
14
|
+
const targetConstructor = target.constructor as EntityConstructor<T>
|
|
15
|
+
const query = new EntityQuery(targetConstructor)
|
|
16
|
+
const queryProperty = query[property as keyof EntityQuery<T>] as Function
|
|
17
|
+
if (typeof queryProperty === 'function') {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
query.table(target.constructor.getTable())
|
|
20
|
+
return (...args: object[]) => {
|
|
21
|
+
return queryProperty.apply(query, args)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const value = target[property as keyof T]
|
|
27
|
+
|
|
28
|
+
if (!value) {
|
|
29
|
+
const relationships = target.metadata.allRelationships
|
|
30
|
+
if (relationships[property]) {
|
|
31
|
+
const relationship = (relationships[property] as any)
|
|
32
|
+
const relationshipClass = new relationship.relationClass(relationships[property], target) as unknown as T[keyof T]
|
|
33
|
+
return target[property as keyof T] = relationshipClass
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
// if (typeof value === 'function') {
|
|
39
|
+
// return value.bind(target);
|
|
40
|
+
// }
|
|
41
|
+
// return value;
|
|
42
|
+
}
|
|
43
|
+
}
|