@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.
Files changed (195) hide show
  1. package/.eslintrc-old.js +208 -0
  2. package/Cache/Cache.ts +13 -0
  3. package/Cache/Stores/DatabaseStore.ts +120 -0
  4. package/Cache/Stores/FileStore.ts +69 -0
  5. package/Cache/Stores/MemoryStore.ts +71 -0
  6. package/Cache/index.ts +5 -0
  7. package/Cache/types.ts +4 -0
  8. package/Container/ClassResolver.ts +106 -0
  9. package/Container/Container.ts +144 -0
  10. package/Container/Contracts.ts +3 -0
  11. package/Container/Decorators.ts +20 -0
  12. package/Container/Metadata.ts +16 -0
  13. package/Container/Resolver.ts +27 -0
  14. package/Container/index.ts +5 -0
  15. package/Database/Clauses/Where.ts +0 -0
  16. package/Database/Connection.ts +52 -0
  17. package/Database/Decorators.ts +142 -0
  18. package/Database/Drivers/DatabaseDriver.ts +37 -0
  19. package/Database/Drivers/MySQL/Alter/index.ts +55 -0
  20. package/Database/Drivers/MySQL/ColumnDefinition.ts +16 -0
  21. package/Database/Drivers/MySQL/Create.ts +47 -0
  22. package/Database/Drivers/MySQL/MysqlDriver.ts +123 -0
  23. package/Database/Drivers/MySQL/Schema.ts +82 -0
  24. package/Database/Drivers/MySQL/index.ts +13 -0
  25. package/Database/Drivers/SQL/Schema.ts +73 -0
  26. package/Database/Drivers/SQL/Statements/Alter.ts +22 -0
  27. package/Database/Drivers/SQL/Statements/BaseStatement.ts +55 -0
  28. package/Database/Drivers/SQL/Statements/Delete.ts +20 -0
  29. package/Database/Drivers/SQL/Statements/Insert.ts +24 -0
  30. package/Database/Drivers/SQL/Statements/Select.ts +55 -0
  31. package/Database/Drivers/SQL/Statements/Update.ts +33 -0
  32. package/Database/Drivers/SQL/index.ts +8 -0
  33. package/Database/Drivers/SQLite/Create.ts +45 -0
  34. package/Database/Drivers/SQLite/Schema.ts +54 -0
  35. package/Database/Drivers/SQLite/SqliteDriver.ts +144 -0
  36. package/Database/Drivers/SQLite/index.ts +15 -0
  37. package/Database/Drivers/SchemaContract.ts +11 -0
  38. package/Database/Drivers/Statement.ts +16 -0
  39. package/Database/Drivers/index.ts +4 -0
  40. package/Database/Entity.ts +377 -0
  41. package/Database/EntityNotFoundError.ts +5 -0
  42. package/Database/EntityProxyHandler.ts +43 -0
  43. package/Database/EntityQuery.ts +197 -0
  44. package/Database/Expression.ts +3 -0
  45. package/Database/Fields.ts +816 -0
  46. package/Database/Helpers.ts +12 -0
  47. package/Database/List.ts +71 -0
  48. package/Database/Migrations/Migration.ts +10 -0
  49. package/Database/Migrations/MigrationHistory.ts +20 -0
  50. package/Database/Migrations/Migrator.ts +122 -0
  51. package/Database/Migrations/index.ts +3 -0
  52. package/Database/ORM/BaseRelationship.ts +10 -0
  53. package/Database/Query.ts +374 -0
  54. package/Database/Seeders/Seeder.ts +3 -0
  55. package/Database/Seeders/SeederManager.ts +27 -0
  56. package/Database/Seeders/index.ts +2 -0
  57. package/Database/StringExpression.ts +9 -0
  58. package/Database/Types.ts +165 -0
  59. package/Database/index.ts +23 -0
  60. package/Encryption/index.ts +51 -0
  61. package/Forms/Decorators.ts +29 -0
  62. package/Forms/Form.ts +70 -0
  63. package/Forms/FormFields.ts +36 -0
  64. package/Forms/index.ts +3 -0
  65. package/Framework/App.ts +10 -0
  66. package/Framework/Application.ts +118 -0
  67. package/Framework/Auth/Auth.ts +122 -0
  68. package/Framework/Auth/Authenticatable.ts +9 -0
  69. package/Framework/Auth/User.ts +27 -0
  70. package/Framework/Auth/index.ts +29 -0
  71. package/Framework/Config/AppConfig.ts +25 -0
  72. package/Framework/Config/AuthConfig.ts +9 -0
  73. package/Framework/Config/BaseConfig.ts +9 -0
  74. package/Framework/Config/CacheConfig.ts +38 -0
  75. package/Framework/Config/DatabaseConfig.ts +21 -0
  76. package/Framework/Config/MailConfig.ts +30 -0
  77. package/Framework/Config/index.ts +6 -0
  78. package/Framework/Middleware/AuthMiddleware.ts +31 -0
  79. package/Framework/Middleware/CorsMiddleware.ts +32 -0
  80. package/Framework/Middleware/StaticAssetsMiddleware.ts +76 -0
  81. package/Framework/Middleware/index.ts +2 -0
  82. package/Framework/Provider.ts +9 -0
  83. package/Framework/Providers/CacheProvider.ts +28 -0
  84. package/Framework/Providers/DatabaseProvider.ts +36 -0
  85. package/Framework/Providers/MailProvider.ts +31 -0
  86. package/Framework/Providers/WebsocketsProvider.ts +120 -0
  87. package/Framework/Resolvers/AuthResolver.ts +23 -0
  88. package/Framework/Resolvers/EntityResolver.ts +41 -0
  89. package/Framework/Resolvers/FormResolver.ts +37 -0
  90. package/Framework/RootDir.ts +1 -0
  91. package/Framework/index.ts +8 -0
  92. package/Frontend/Angular/index.ts +24 -0
  93. package/Frontend/Websockets/index.ts +86 -0
  94. package/Mail/MailAddress.ts +4 -0
  95. package/Mail/MailTransport.ts +6 -0
  96. package/Mail/Mailable.ts +16 -0
  97. package/Mail/Mailer.ts +27 -0
  98. package/Mail/PendingMail.ts +68 -0
  99. package/Mail/SentMessage.ts +20 -0
  100. package/Mail/Transports/MemoryTransport.ts +13 -0
  101. package/Mail/Transports/SendGridTransport.ts +95 -0
  102. package/Mail/index.ts +3 -0
  103. package/Models/Model.ts +41 -0
  104. package/Models/index.ts +54 -0
  105. package/Router/BaseController.ts +7 -0
  106. package/Router/Decorators.ts +224 -0
  107. package/Router/Guard.ts +10 -0
  108. package/Router/Http/Contracts.ts +4 -0
  109. package/Router/Http/ErrorHandler.ts +45 -0
  110. package/Router/Http/ErrorHandlerInterface.ts +6 -0
  111. package/Router/Http/Errors/HttpError.ts +15 -0
  112. package/Router/Http/Handler.ts +183 -0
  113. package/Router/Http/HttpRoute.ts +110 -0
  114. package/Router/Http/Middleware.ts +7 -0
  115. package/Router/Http/Request.ts +225 -0
  116. package/Router/Http/Response.ts +35 -0
  117. package/Router/Http/index.ts +85 -0
  118. package/Router/Metadata.ts +34 -0
  119. package/Router/Middleware.ts +16 -0
  120. package/Router/Request.ts +65 -0
  121. package/Router/Route.ts +16 -0
  122. package/Router/RouteNotFoundError.ts +5 -0
  123. package/Router/Router.ts +35 -0
  124. package/Router/Servers/index.ts +6 -0
  125. package/Router/Servers/node.ts +49 -0
  126. package/Router/Servers/uNetworking.ts +151 -0
  127. package/Router/Websockets/Handler.ts +82 -0
  128. package/Router/Websockets/Middleware.ts +7 -0
  129. package/Router/Websockets/Request.ts +3 -0
  130. package/Router/Websockets/WebsocketRoute.ts +61 -0
  131. package/Router/Websockets/index.ts +57 -0
  132. package/Router/Websockets/types.ts +23 -0
  133. package/Router/index.ts +6 -0
  134. package/Storage/File.ts +30 -0
  135. package/Storage/Image.ts +19 -0
  136. package/Storage/InvalidImage.ts +3 -0
  137. package/Storage/Storage.ts +98 -0
  138. package/Storage/index.ts +4 -0
  139. package/Storage/utils.ts +18 -0
  140. package/Support/Array.ts +184 -0
  141. package/Support/Math.ts +12 -0
  142. package/Support/Metadata.ts +31 -0
  143. package/Support/String.ts +38 -0
  144. package/Support/index.ts +19 -0
  145. package/Support/utils.ts +7 -0
  146. package/Testing/TestCase.ts +193 -0
  147. package/Validation/Rule.ts +9 -0
  148. package/Validation/RuleInterface.ts +10 -0
  149. package/Validation/Rules/Email.ts +20 -0
  150. package/Validation/Rules/FileExtension.ts +21 -0
  151. package/Validation/Rules/MaxLength.ts +17 -0
  152. package/Validation/Rules/MinLength.ts +18 -0
  153. package/Validation/Rules/Optional.ts +18 -0
  154. package/Validation/Rules/Required.ts +29 -0
  155. package/Validation/Validator.ts +7 -0
  156. package/Validation/index.ts +12 -0
  157. package/benchmark-index.js +20 -0
  158. package/benchmark.ts +47 -0
  159. package/package.json +7 -3
  160. package/tests/Cache/DatabaseStoreTest.ts +110 -0
  161. package/tests/Cache/FileStoreTest.ts +93 -0
  162. package/tests/Cache/MemoryStoreTest.ts +92 -0
  163. package/tests/Container/ContainerTest.ts +336 -0
  164. package/tests/Database/Drivers/MySQL/AlterTest.ts +100 -0
  165. package/tests/Database/Drivers/MySQL/CreateTest.ts +39 -0
  166. package/tests/Database/Drivers/MysqlDriverTest.ts +140 -0
  167. package/tests/Database/Drivers/SqliteDriverTest.ts +126 -0
  168. package/tests/Database/Entities/Article.ts +21 -0
  169. package/tests/Database/Entities/Profile.ts +21 -0
  170. package/tests/Database/Entities/Role.ts +15 -0
  171. package/tests/Database/Entities/User.ts +35 -0
  172. package/tests/Database/EntityRelationshipsTest.ts +475 -0
  173. package/tests/Database/EntityTest.ts +202 -0
  174. package/tests/Database/Migrations/RunnerTest.ts +96 -0
  175. package/tests/Database/Migrations/migrations/1.createUserTable.ts +12 -0
  176. package/tests/Database/Migrations/migrations/2.createArticlesTable.ts +12 -0
  177. package/tests/Database/QueryTest.ts +573 -0
  178. package/tests/Database/SchemaTest.ts +32 -0
  179. package/tests/Database/Seeders/RunnerTest.ts +30 -0
  180. package/tests/Database/Seeders/seeders/RandomSeeder.ts +10 -0
  181. package/tests/Database/utils.ts +16 -0
  182. package/tests/Forms/FormsTest.ts +59 -0
  183. package/tests/Framework/Middleware/StaticAssetsMiddlewareTest.ts +129 -0
  184. package/tests/Framework/Resolvers/EntityResolverTest.ts +48 -0
  185. package/tests/Framework/Router/RouterTest.ts +33 -0
  186. package/tests/Mail/MailerTest.ts +76 -0
  187. package/tests/Model/ModelTest.ts +183 -0
  188. package/tests/Router/HttpHandlerTest.ts +72 -0
  189. package/tests/Router/RequestTest.ts +77 -0
  190. package/tests/Router/WebSocketsHandlerTest.ts +51 -0
  191. package/tests/Storage/StorageTest.ts +97 -0
  192. package/tests/Support/ArrayTest.ts +167 -0
  193. package/tests/Support/StringTest.ts +18 -0
  194. package/tests/Validation/RulesTest.ts +54 -0
  195. 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,9 @@
1
+ import { User } from '../Auth'
2
+ import { BaseConfig } from './BaseConfig'
3
+
4
+ export class AuthConfig extends BaseConfig<AuthConfig> {
5
+ entity: typeof User
6
+ signature: string
7
+ duration: number
8
+ saltRounds: number
9
+ }
@@ -0,0 +1,9 @@
1
+ import { ChildObject } from '../../Support'
2
+
3
+ export class BaseConfig<T> {
4
+ constructor(private newValues: Partial<ChildObject<T, BaseConfig<T>>>) {}
5
+
6
+ applyNewValues() {
7
+ Object.assign(this, this.newValues)
8
+ }
9
+ }
@@ -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,6 @@
1
+ export * from './BaseConfig'
2
+ export * from './AppConfig'
3
+ export * from './AuthConfig'
4
+ export * from './DatabaseConfig'
5
+ export * from './CacheConfig'
6
+ export * from './MailConfig'
@@ -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,2 @@
1
+ export * from './AuthMiddleware'
2
+ export * from './CorsMiddleware'
@@ -0,0 +1,9 @@
1
+ import { Container, Inject } from '@Typetron/Container'
2
+
3
+ export abstract class Provider {
4
+
5
+ @Inject()
6
+ public app: Container
7
+
8
+ abstract register(): void | Promise<void>;
9
+ }
@@ -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'