@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,29 @@
|
|
|
1
|
+
import { Container, InjectableMetadata, Scope } from '../../Container'
|
|
2
|
+
import { ControllerMetadata, MethodMetadata } from '../../Router/Metadata'
|
|
3
|
+
|
|
4
|
+
export * from './Auth'
|
|
5
|
+
export * from './User'
|
|
6
|
+
export * from './Authenticatable'
|
|
7
|
+
|
|
8
|
+
export const AuthUserIdentifier = Symbol('framework.auth:userIdentifier')
|
|
9
|
+
|
|
10
|
+
export function AuthUser() {
|
|
11
|
+
return function(target: object, property: string, parameterIndex?: number) {
|
|
12
|
+
if (parameterIndex === undefined) {
|
|
13
|
+
const metadata = InjectableMetadata.get(target.constructor)
|
|
14
|
+
metadata.dependencies[property] = AuthUserIdentifier
|
|
15
|
+
metadata.scope = Scope.REQUEST
|
|
16
|
+
InjectableMetadata.set(metadata, target.constructor)
|
|
17
|
+
} else {
|
|
18
|
+
const metadata = ControllerMetadata.get(target.constructor)
|
|
19
|
+
|
|
20
|
+
const methodMetadata = metadata.methods[property] || new MethodMetadata()
|
|
21
|
+
methodMetadata.parametersOverrides[parameterIndex] = async function(container: Container) {
|
|
22
|
+
return await container.get(AuthUserIdentifier)
|
|
23
|
+
}
|
|
24
|
+
metadata.methods[property] = methodMetadata
|
|
25
|
+
|
|
26
|
+
ControllerMetadata.set(metadata, target.constructor)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseConfig } from './BaseConfig'
|
|
2
|
+
import { GlobalMiddleware } from '../../Router'
|
|
3
|
+
import { Abstract, Type } from '../../Support'
|
|
4
|
+
import { Provider } from '../Provider'
|
|
5
|
+
import { HttpMiddleware } from '@Typetron/Router/Http/Middleware'
|
|
6
|
+
import { WebsocketMiddleware } from '@Typetron/Router/Websockets/Middleware'
|
|
7
|
+
|
|
8
|
+
export class AppConfig extends BaseConfig<AppConfig> {
|
|
9
|
+
environment: string
|
|
10
|
+
server: 'node' | 'uNetworking' = 'node'
|
|
11
|
+
debug = true
|
|
12
|
+
port: number
|
|
13
|
+
websocketsPort?: number
|
|
14
|
+
middleware: {
|
|
15
|
+
global: Abstract<GlobalMiddleware>[],
|
|
16
|
+
http: Abstract<HttpMiddleware>[],
|
|
17
|
+
websocket: Abstract<WebsocketMiddleware>[],
|
|
18
|
+
} = {
|
|
19
|
+
global: [],
|
|
20
|
+
http: [],
|
|
21
|
+
websocket: [],
|
|
22
|
+
}
|
|
23
|
+
providers: Type<Provider> []
|
|
24
|
+
staticAssets?: {url: string, path: string, basePath?: boolean, indexFile?: string}[]
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BaseConfig } from './BaseConfig'
|
|
2
|
+
|
|
3
|
+
export type CacheStoreKey = keyof CacheConfig['drivers']
|
|
4
|
+
|
|
5
|
+
export class CacheConfig extends BaseConfig<CacheConfig> {
|
|
6
|
+
default: CacheStoreKey = process.env?.CACHE_DRIVER as CacheStoreKey ?? 'file'
|
|
7
|
+
|
|
8
|
+
drivers = {
|
|
9
|
+
file: {
|
|
10
|
+
path: 'cache/data',
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
memory: {},
|
|
14
|
+
|
|
15
|
+
database: {
|
|
16
|
+
table: 'cache',
|
|
17
|
+
connection: null,
|
|
18
|
+
},
|
|
19
|
+
//
|
|
20
|
+
// redis: {
|
|
21
|
+
// connection: 'cache',
|
|
22
|
+
// lock_connection: 'default',
|
|
23
|
+
// },
|
|
24
|
+
//
|
|
25
|
+
// memcached: {
|
|
26
|
+
// persistent_id: process.env.MEMCACHED_PERSISTENT_ID,
|
|
27
|
+
// sasl: [process.env.MEMCACHED_USERNAME, process.env.MEMCACHED_PASSWORD],
|
|
28
|
+
// options: {
|
|
29
|
+
// // Memcached::OPT_CONNECT_TIMEOUT : 2000,
|
|
30
|
+
// },
|
|
31
|
+
// servers: {
|
|
32
|
+
// host: process.env.MEMCACHED_HOST ?? '127.0.0.1',
|
|
33
|
+
// port: process.env.MEMCACHED_PORT ?? '11211',
|
|
34
|
+
// weight: 100,
|
|
35
|
+
// },
|
|
36
|
+
// },
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseConfig } from './BaseConfig'
|
|
2
|
+
import { MysqlDriver, SqliteDriver } from '@Typetron/Database'
|
|
3
|
+
import { DatabaseDriver } from '@Typetron/Database/Drivers/DatabaseDriver'
|
|
4
|
+
|
|
5
|
+
export class DatabaseConfig extends BaseConfig<DatabaseConfig> {
|
|
6
|
+
synchronizeSchema = false
|
|
7
|
+
entities: string
|
|
8
|
+
migrationsDirectory = 'Database/migrations'
|
|
9
|
+
seedersDirectory = 'Database/seeders'
|
|
10
|
+
driver: keyof this['drivers'] = process.env.databaseDriver as keyof this['drivers'] ?? 'sqlite'
|
|
11
|
+
|
|
12
|
+
drivers: Record<string, () => DatabaseDriver> = {
|
|
13
|
+
sqlite: () => new SqliteDriver(process.env.database ?? 'database.sqlite'),
|
|
14
|
+
mysql: () => new MysqlDriver({
|
|
15
|
+
host: process.env.databaseHost,
|
|
16
|
+
user: process.env.databaseUser,
|
|
17
|
+
password: process.env.databasePassword,
|
|
18
|
+
database: process.env.database,
|
|
19
|
+
}),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseConfig } from './BaseConfig'
|
|
2
|
+
|
|
3
|
+
export type Mailers = keyof MailConfig['mailers']
|
|
4
|
+
|
|
5
|
+
export class MailConfig extends BaseConfig<MailConfig> {
|
|
6
|
+
default: Mailers = process.env?.MAILER as Mailers ?? 'memory'
|
|
7
|
+
|
|
8
|
+
from: {
|
|
9
|
+
email: string,
|
|
10
|
+
name?: string,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
mailers: {
|
|
14
|
+
memory: any,
|
|
15
|
+
|
|
16
|
+
SendGrid?: {
|
|
17
|
+
key: string,
|
|
18
|
+
},
|
|
19
|
+
SES?: {
|
|
20
|
+
// TODO
|
|
21
|
+
},
|
|
22
|
+
Mailgun?: {
|
|
23
|
+
// TODO
|
|
24
|
+
},
|
|
25
|
+
Postmark?: {
|
|
26
|
+
// TODO
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Inject, Injectable } from '../../Container'
|
|
2
|
+
import { RequestHandler } from '../../Router'
|
|
3
|
+
import { Http, HttpError, Request } from '../../Router/Http'
|
|
4
|
+
import * as jwt from 'jsonwebtoken'
|
|
5
|
+
import { Auth } from '../Auth'
|
|
6
|
+
import { HttpMiddleware } from '@Typetron/Router/Http/Middleware'
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class AuthMiddleware implements HttpMiddleware {
|
|
10
|
+
|
|
11
|
+
@Inject()
|
|
12
|
+
auth: Auth
|
|
13
|
+
|
|
14
|
+
async handle(request: Request, next: RequestHandler) {
|
|
15
|
+
if (this.auth.id && this.auth.expiresAt > new Date()) {
|
|
16
|
+
return next(request)
|
|
17
|
+
}
|
|
18
|
+
const authHeader = request.headers.authorization || (request.body as Record<string, string>)?.token || ''
|
|
19
|
+
const token = authHeader.replace('Bearer ', '')
|
|
20
|
+
try {
|
|
21
|
+
await this.auth.verify(token)
|
|
22
|
+
return next(request)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error instanceof jwt.TokenExpiredError || error instanceof jwt.JsonWebTokenError) {
|
|
25
|
+
throw new HttpError('Unauthenticated', Http.Status.UNAUTHORIZED)
|
|
26
|
+
}
|
|
27
|
+
throw new HttpError((error as Error).message, Http.Status.BAD_REQUEST)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Inject, Injectable } from '../../Container'
|
|
2
|
+
import { RequestHandler } from '../../Router'
|
|
3
|
+
import { ErrorHandlerInterface, Request, Response } from '../../Router/Http'
|
|
4
|
+
import { HttpMiddleware } from '@Typetron/Router/Http/Middleware'
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class CorsMiddleware extends HttpMiddleware {
|
|
8
|
+
|
|
9
|
+
@Inject()
|
|
10
|
+
errorHandler: ErrorHandlerInterface
|
|
11
|
+
|
|
12
|
+
async handle(request: Request, next: RequestHandler) {
|
|
13
|
+
let response: Response
|
|
14
|
+
if (request.method.toLowerCase() === 'options') {
|
|
15
|
+
response = new Response(undefined)
|
|
16
|
+
response.setHeaders({
|
|
17
|
+
'Access-Control-Allow-Methods': 'GET, PUT, PATCH, POST, DELETE',
|
|
18
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
19
|
+
})
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
response = await next(request)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
response = await this.errorHandler.handle(error as Error, request)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
response.setHeader('Access-Control-Allow-Origin', '*')
|
|
29
|
+
return response
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Inject, Injectable } from '../../Container'
|
|
2
|
+
import { RequestHandler, RouteNotFoundError } from '../../Router'
|
|
3
|
+
import { Storage } from '../../Storage'
|
|
4
|
+
import { Http, Request, Response } from '../../Router/Http'
|
|
5
|
+
import { AppConfig } from '../Config'
|
|
6
|
+
import * as path from 'path'
|
|
7
|
+
import { HttpMiddleware } from '@Typetron/Router/Http/Middleware'
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class StaticAssetsMiddleware extends HttpMiddleware {
|
|
11
|
+
|
|
12
|
+
@Inject()
|
|
13
|
+
appConfig: AppConfig
|
|
14
|
+
|
|
15
|
+
@Inject()
|
|
16
|
+
storage: Storage
|
|
17
|
+
|
|
18
|
+
mimeTypes: {[key: string]: string} = {
|
|
19
|
+
'txt': 'text/plain',
|
|
20
|
+
'js': 'text/javascript',
|
|
21
|
+
'html': 'text/html',
|
|
22
|
+
'css': 'text/css',
|
|
23
|
+
'jpg': 'image/jpeg',
|
|
24
|
+
'jpeg': 'image/jpeg',
|
|
25
|
+
'png': 'image/png',
|
|
26
|
+
'webp': 'image/webp',
|
|
27
|
+
'gif': 'image/gif',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: Request, next: RequestHandler) {
|
|
31
|
+
try {
|
|
32
|
+
return await next(request)
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (request.method.toLowerCase() === Http.Method.GET.toLowerCase() && error instanceof RouteNotFoundError) {
|
|
35
|
+
return this.loadStaticAsset(error, request)
|
|
36
|
+
}
|
|
37
|
+
throw error
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async loadStaticAsset(error: RouteNotFoundError, request: Request): Promise<Response> {
|
|
42
|
+
const configs = this.appConfig.staticAssets ?? []
|
|
43
|
+
for (const config of configs) {
|
|
44
|
+
if (!request.uri.match(config.url)) {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let realPath = path.join(config.path, request.uri)
|
|
49
|
+
let extension = path.extname(realPath).substring(1)
|
|
50
|
+
if (!extension) {
|
|
51
|
+
realPath = path.join(realPath, 'index.html')
|
|
52
|
+
extension = 'html'
|
|
53
|
+
}
|
|
54
|
+
if (!await this.storage.exists(realPath)) {
|
|
55
|
+
if (config.basePath) {
|
|
56
|
+
const basePath = path.join(config.path, config.indexFile ?? 'index.html')
|
|
57
|
+
|
|
58
|
+
return this.getResponse(basePath)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
return this.getResponse(realPath, extension)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw error
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async getResponse(filePath: string, extension?: string) {
|
|
70
|
+
extension = extension ?? path.extname(filePath).substring(1)
|
|
71
|
+
const file = await this.storage.read(filePath)
|
|
72
|
+
const contentType = this.mimeTypes[extension || 'application/octet-stream'] || this.mimeTypes.txt
|
|
73
|
+
return new Response(file, Http.Status.OK, {'Content-type': contentType})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { Container, Inject } from '@Typetron/Container'
|
|
4
|
+
import { Cache, DatabaseStore, FileStore, MemoryStore } from '@Typetron/Cache'
|
|
5
|
+
import { Storage } from '@Typetron/Storage'
|
|
6
|
+
import { Provider } from '../Provider'
|
|
7
|
+
import { CacheConfig, CacheStoreKey } from '../Config'
|
|
8
|
+
|
|
9
|
+
const cacheStores: Record<CacheStoreKey, (...args: any[]) => Cache> = {
|
|
10
|
+
file: (app: Container, config: CacheConfig) => {
|
|
11
|
+
return new FileStore(app.get(Storage), config.drivers.file.path)
|
|
12
|
+
},
|
|
13
|
+
memory: () => {
|
|
14
|
+
return new MemoryStore()
|
|
15
|
+
},
|
|
16
|
+
database: (app: Container, config: CacheConfig) => {
|
|
17
|
+
return new DatabaseStore(config.drivers.database.table)
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class CacheProvider extends Provider {
|
|
22
|
+
@Inject()
|
|
23
|
+
config: CacheConfig
|
|
24
|
+
|
|
25
|
+
public register() {
|
|
26
|
+
this.app.set(Cache, cacheStores[this.config.default](this.app, this.config))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Provider } from '../Provider'
|
|
2
|
+
import { DatabaseConfig } from '../Config'
|
|
3
|
+
import { Inject } from '../../Container'
|
|
4
|
+
import { Connection, Entity, Query } from '@Typetron/Database'
|
|
5
|
+
import { Storage } from '../../Storage'
|
|
6
|
+
|
|
7
|
+
export class DatabaseProvider extends Provider {
|
|
8
|
+
|
|
9
|
+
@Inject()
|
|
10
|
+
databaseConfig: DatabaseConfig
|
|
11
|
+
|
|
12
|
+
@Inject()
|
|
13
|
+
storage: Storage
|
|
14
|
+
|
|
15
|
+
async register() {
|
|
16
|
+
const driver = this.databaseConfig.drivers[this.databaseConfig.driver]
|
|
17
|
+
if (!driver) {
|
|
18
|
+
throw new Error(`Driver '${this.databaseConfig.driver}' is not defined in the 'database.drivers' config`)
|
|
19
|
+
}
|
|
20
|
+
Query.connection = new Connection(driver())
|
|
21
|
+
if (this.databaseConfig.synchronizeSchema) {
|
|
22
|
+
await this.synchronize(Query.connection)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async synchronize(connection: Connection) {
|
|
27
|
+
const entityFiles = await this.storage.files(this.databaseConfig.entities, true)
|
|
28
|
+
const entitiesImports: (typeof Entity)[] = await Promise.all(
|
|
29
|
+
entityFiles.map(file => import(file.path))
|
|
30
|
+
)
|
|
31
|
+
const entitiesMetadata = entitiesImports.flatMap(item => Object.values(item).pluck('metadata'))
|
|
32
|
+
|
|
33
|
+
await connection.driver.schema.synchronize(entitiesMetadata)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { Container, Inject } from '@Typetron/Container'
|
|
4
|
+
import { Provider } from '../Provider'
|
|
5
|
+
import { MailConfig, Mailers } from '../Config'
|
|
6
|
+
import { Mailer, MemoryTransport } from '@Typetron/Mail'
|
|
7
|
+
import { MailTransport } from '@Typetron/Mail/MailTransport'
|
|
8
|
+
import { SendGridTransport } from '@Typetron/Mail/Transports/SendGridTransport'
|
|
9
|
+
|
|
10
|
+
const mailers: Partial<Record<Mailers, (...args: any[]) => MailTransport>> = {
|
|
11
|
+
memory: (app: Container, config: MailConfig) => {
|
|
12
|
+
return new MemoryTransport()
|
|
13
|
+
},
|
|
14
|
+
SendGrid: (app: Container, config: MailConfig) => {
|
|
15
|
+
if (!config.mailers.SendGrid?.key) {
|
|
16
|
+
throw new Error('SendGrid key is not set')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return new SendGridTransport(config.mailers.SendGrid?.key)
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class MailProvider extends Provider {
|
|
24
|
+
@Inject()
|
|
25
|
+
config: MailConfig
|
|
26
|
+
|
|
27
|
+
public register() {
|
|
28
|
+
const transport = mailers[this.config.default]?.(this.app, this.config)
|
|
29
|
+
this.app.set(Mailer, new Mailer(this.config.from, transport))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Provider } from '../Provider'
|
|
2
|
+
import { Container, Inject } from '../../Container'
|
|
3
|
+
import { AppConfig } from '../Config'
|
|
4
|
+
import { App, DEDICATED_COMPRESSOR_3KB, WebSocket as uWebSocket } from 'uWebSockets.js'
|
|
5
|
+
import { TextDecoder } from 'util'
|
|
6
|
+
import { ErrorHandlerInterface, HttpError, Response } from '../../Router/Http'
|
|
7
|
+
import { ActionErrorResponse, Handler, WebSocket } from '../../Router/Websockets'
|
|
8
|
+
import { ActionRequest, ActionResponse } from '@Typetron/Router/Websockets'
|
|
9
|
+
import { Request } from '@Typetron/Router/Websockets/Request'
|
|
10
|
+
|
|
11
|
+
enum WebsocketMessageStatus {
|
|
12
|
+
OK = 'OK',
|
|
13
|
+
Error = 'Error',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class WebsocketsProvider extends Provider {
|
|
17
|
+
|
|
18
|
+
@Inject()
|
|
19
|
+
appConfig: AppConfig
|
|
20
|
+
|
|
21
|
+
@Inject()
|
|
22
|
+
handler: Handler
|
|
23
|
+
|
|
24
|
+
@Inject()
|
|
25
|
+
errorHandler: ErrorHandlerInterface
|
|
26
|
+
|
|
27
|
+
sockets = new Map<string | number, WebSocket>()
|
|
28
|
+
|
|
29
|
+
register(): void | Promise<void> {
|
|
30
|
+
const port = this.appConfig.websocketsPort
|
|
31
|
+
|
|
32
|
+
if (!port) {
|
|
33
|
+
throw new Error('Please define a port for the Websockets server.')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stringDecoder = new TextDecoder()
|
|
37
|
+
|
|
38
|
+
const app = App().ws('/', {
|
|
39
|
+
|
|
40
|
+
/* There are many common helper features */
|
|
41
|
+
idleTimeout: 40,
|
|
42
|
+
maxBackpressure: 1024,
|
|
43
|
+
maxPayloadLength: 1024 * 1000 * 5,
|
|
44
|
+
compression: DEDICATED_COMPRESSOR_3KB,
|
|
45
|
+
|
|
46
|
+
open: (socket: uWebSocket<any>) => {
|
|
47
|
+
const socketWrapper = new WebSocket(socket, this.app.createChildContainer())
|
|
48
|
+
this.handler.onOpen?.run(socketWrapper.connection.container, {})
|
|
49
|
+
},
|
|
50
|
+
close: async (socket: uWebSocket<any>) => {
|
|
51
|
+
const extendedSocket = socket as uWebSocket<any> & {container: Container}
|
|
52
|
+
await this.handler.onClose?.run(extendedSocket.container, {}).catch(error => {
|
|
53
|
+
console.info('socket close error ->', error)
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/* For brevity we skip the other actions (upgrade, open, ping, pong, close) */
|
|
58
|
+
message: async (socket, message, isBinary) => {
|
|
59
|
+
const jsonData = stringDecoder.decode(message)
|
|
60
|
+
let data: ActionRequest
|
|
61
|
+
try {
|
|
62
|
+
data = JSON.parse(jsonData)
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error(`Invalid Websocket message sent: ${jsonData}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const extendedSocket = socket as uWebSocket<any> & {container: Container}
|
|
69
|
+
const response = await this.handleAction(extendedSocket.container, data)
|
|
70
|
+
const action = data.action
|
|
71
|
+
const sentResponse: ActionResponse<unknown> = {
|
|
72
|
+
action,
|
|
73
|
+
message: response.body,
|
|
74
|
+
status: WebsocketMessageStatus.OK,
|
|
75
|
+
}
|
|
76
|
+
if (response.status >= 200 && response.status <= 399) {
|
|
77
|
+
sentResponse.status = WebsocketMessageStatus.OK
|
|
78
|
+
} else {
|
|
79
|
+
sentResponse.status = WebsocketMessageStatus.Error
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
socket.send(JSON.stringify(sentResponse), isBinary, true)
|
|
83
|
+
|
|
84
|
+
} catch (err) {
|
|
85
|
+
const error = err as HttpError
|
|
86
|
+
const errorMessage: ActionErrorResponse = {
|
|
87
|
+
message: {
|
|
88
|
+
message: String(error.content ?? error.message),
|
|
89
|
+
stack: error.stack ?? '<could not generate stack>'
|
|
90
|
+
},
|
|
91
|
+
action: data.action,
|
|
92
|
+
status: WebsocketMessageStatus.Error
|
|
93
|
+
}
|
|
94
|
+
if (errorMessage.message) {
|
|
95
|
+
const response = await this.errorHandler.handle(error) as Response<Error>
|
|
96
|
+
errorMessage.message.message = response.body.message
|
|
97
|
+
}
|
|
98
|
+
console.error(errorMessage)
|
|
99
|
+
socket.send(JSON.stringify(errorMessage), isBinary, true)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
}).listen(port, (listenSocket) => {
|
|
105
|
+
if (listenSocket) {
|
|
106
|
+
console.info(`Websocket server started on port ${port}`)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private async handleAction(container: Container, {action, message}: ActionRequest) {
|
|
112
|
+
const request = new Request(action, message?.body)
|
|
113
|
+
|
|
114
|
+
// TODO find a way to save parameters so they can be retrieved easily in the Resolvers (check routeEntity resolver for example)
|
|
115
|
+
message?.parameters?.forEach(parameter => {
|
|
116
|
+
request.parameters[String.random(15)] = parameter
|
|
117
|
+
})
|
|
118
|
+
return this.handler.handle(container, request)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseResolver } from '../../Container'
|
|
2
|
+
import { Auth, AuthUserIdentifier } from '../Auth'
|
|
3
|
+
|
|
4
|
+
export class AuthResolver extends BaseResolver {
|
|
5
|
+
|
|
6
|
+
async resolve<T>(abstract: symbol, parameters: object[]): Promise<T> {
|
|
7
|
+
const auth = this.container.get(Auth)
|
|
8
|
+
if (!auth.id) {
|
|
9
|
+
return undefined as unknown as T
|
|
10
|
+
}
|
|
11
|
+
return await auth.user() as unknown as T
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
canResolve<T>(abstract: symbol): boolean {
|
|
15
|
+
return abstract === AuthUserIdentifier
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// private setScopeToRequest() {
|
|
19
|
+
// const metadata = InjectableMetadata.get(User);
|
|
20
|
+
// metadata.scope = Scope.REQUEST;
|
|
21
|
+
// InjectableMetadata.set(metadata, User);
|
|
22
|
+
// }
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Constructor } from '../../Support'
|
|
2
|
+
import { Entity, EntityConstructor } from '../../Database'
|
|
3
|
+
import { Request } from '../../Router/Request'
|
|
4
|
+
import { BaseResolver, Container, InjectableMetadata, Scope } from '../../Container'
|
|
5
|
+
import { EntityNotFoundError } from '../../Database/EntityNotFoundError'
|
|
6
|
+
|
|
7
|
+
export class EntityResolver extends BaseResolver {
|
|
8
|
+
|
|
9
|
+
constructor(container: Container) {
|
|
10
|
+
super(container)
|
|
11
|
+
this.setEntityScopeToRequest()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async resolve<T>(abstract: EntityConstructor<Entity> & typeof Entity, parametersValues: object[]): Promise<T> {
|
|
15
|
+
let entity: Entity | undefined
|
|
16
|
+
const request = this.container.get(Request)
|
|
17
|
+
const requestParameterName = abstract.name
|
|
18
|
+
const parameter = request.parameters[requestParameterName] ?? Number(parametersValues[0])
|
|
19
|
+
if (parameter) {
|
|
20
|
+
entity = await abstract.find(parameter)
|
|
21
|
+
if (!entity) {
|
|
22
|
+
throw new EntityNotFoundError(abstract, `Entity '${requestParameterName}' with ${abstract.getPrimaryKey()} '${parameter}' not found`)
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error(`No parameter found that can be used as an entity identifier for the '${requestParameterName}' entity. Did you forget to add the '{${requestParameterName}}' parameter on the route?`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
return entity
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
canResolve<T>(abstract: Constructor<T>): boolean {
|
|
33
|
+
return abstract.prototype instanceof Entity
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private setEntityScopeToRequest() {
|
|
37
|
+
const metadata = InjectableMetadata.get(Entity)
|
|
38
|
+
metadata.scope = Scope.TRANSIENT
|
|
39
|
+
InjectableMetadata.set(metadata, Entity)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseResolver, ClassResolver, Container } from '../../Container'
|
|
2
|
+
import { Constructor } from '../../Support'
|
|
3
|
+
import { Form, FormFields } from '../../Forms'
|
|
4
|
+
import { Http, HttpError } from '../../Router/Http'
|
|
5
|
+
import { Request } from '../../Router/Request'
|
|
6
|
+
|
|
7
|
+
export class FormResolver extends BaseResolver {
|
|
8
|
+
|
|
9
|
+
constructor(container: Container) {
|
|
10
|
+
super(container)
|
|
11
|
+
this.setFormScopeToRequest()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async resolve<T>(abstract: Constructor<T & Form>, parameters: object[]) {
|
|
15
|
+
const request = this.container.get(Request)
|
|
16
|
+
const classResolver = new ClassResolver(this.container)
|
|
17
|
+
const form = await classResolver.resolve(abstract, parameters)
|
|
18
|
+
form.fill({...request.body as object, ...request.files} as FormFields<Form>)
|
|
19
|
+
|
|
20
|
+
if (!form.valid()) {
|
|
21
|
+
throw new HttpError(form.errors, Http.Status.UNPROCESSABLE_ENTITY)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return form
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
canResolve<T>(abstract: Constructor<T>): boolean {
|
|
28
|
+
return abstract.prototype instanceof Form
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private setFormScopeToRequest() {
|
|
32
|
+
// const metadata = InjectableMetadata.get(Form)
|
|
33
|
+
//
|
|
34
|
+
// metadata.scope = Scope.TRANSIENT
|
|
35
|
+
// InjectableMetadata.set(metadata, Form)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class RootDir extends String {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './Application'
|
|
2
|
+
export * from './Provider'
|
|
3
|
+
export * from './App'
|
|
4
|
+
export * from './RootDir'
|
|
5
|
+
export * from './Config'
|
|
6
|
+
export * from './Providers/DatabaseProvider'
|
|
7
|
+
export * from './Providers/CacheProvider'
|
|
8
|
+
export * from './Providers/MailProvider'
|