@kunk/server 3.0.4 → 3.0.6
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/package.json +11 -1
- package/.turbo/turbo-build.log +0 -16
- package/.turbo/turbo-deploy.log +0 -0
- package/.turbo/turbo-version$colon$patch.log +0 -4
- package/CHANGELOG.md +0 -13
- package/src/command.ts +0 -56
- package/src/configs/+.ts +0 -1
- package/src/configs/DatabaseConfig.ts +0 -12
- package/src/context.ts +0 -97
- package/src/contracts/+.ts +0 -5
- package/src/contracts/Auth.ts +0 -73
- package/src/contracts/Database.ts +0 -40
- package/src/contracts/Query.ts +0 -3
- package/src/index.ts +0 -11
- package/src/interfaces/+.ts +0 -9
- package/src/interfaces/IApplication.ts +0 -6
- package/src/interfaces/IContainer.ts +0 -1
- package/src/interfaces/IContext.ts +0 -31
- package/src/interfaces/IContextCommand.ts +0 -7
- package/src/interfaces/IContextHandler.ts +0 -6
- package/src/interfaces/IMiddleware.ts +0 -6
- package/src/interfaces/IService.ts +0 -1
- package/src/middleware.ts +0 -13
- package/src/registry.ts +0 -11
- package/src/server.ts +0 -87
- package/src/services/+.ts +0 -4
- package/src/services/BunGenerator.ts +0 -13
- package/src/services/DizzleDatabase.ts +0 -142
- package/src/services/DrizzleQuery.ts +0 -1
- package/src/services/PinoLogger.ts +0 -36
- package/src/setup.ts +0 -26
- package/src/utils/+.ts +0 -2
- package/src/utils/SERVER_ERROR.ts +0 -41
- package/src/utils/config.ts +0 -70
- package/tsconfig.json +0 -19
- package/tsdown.config.ts +0 -15
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kunk/server",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"module": "./src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
8
11
|
"scripts": {
|
|
9
12
|
"build": "tsdown",
|
|
10
13
|
"version:patch": "bun pm version patch",
|
|
11
14
|
"deploy": "bun publish"
|
|
12
15
|
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
13
23
|
"dependencies": {
|
|
14
24
|
"@kunk/api": "3.0.0",
|
|
15
25
|
"@kunk/tsconfig": "3.0.0",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
[0m[2m[35m$[0m [2m[1mtsdown[0m
|
|
3
|
-
[34mℹ[39m tsdown [2mv0.20.3[22m powered by rolldown [2mv1.0.0-rc.3[22m
|
|
4
|
-
[34mℹ[39m config file: [4m/Users/ag/auxo/kunk/packages/server/tsdown.config.ts[24m
|
|
5
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
6
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
7
|
-
[34mℹ[39m Build start
|
|
8
|
-
|
|
9
|
-
[43m WARN [49m Consider adding [34minlineOnly[39m option to avoid unintended bundling of dependencies, or set [34minlineOnly: false[39m to disable this warning.
|
|
10
|
-
Detected dependencies in bundle:
|
|
11
|
-
- [33mzod[39m
|
|
12
|
-
|
|
13
|
-
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m 7.66 kB[22m [2m│ gzip: 2.96 kB[22m
|
|
14
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m116.22 kB[22m [2m│ gzip: 23.02 kB[22m
|
|
15
|
-
[34mℹ[39m 2 files, total: 123.88 kB
|
|
16
|
-
[32m✔[39m Build complete in [32m2381ms[39m
|
package/.turbo/turbo-deploy.log
DELETED
|
File without changes
|
package/CHANGELOG.md
DELETED
package/src/command.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { I, ICommand, ICommandHandler, IContextCommand, IMiddleware, StandardSchemaV1 } from "@/interfaces/+"
|
|
2
|
-
import { SERVER_ERROR } from "@/utils/+"
|
|
3
|
-
import { middleware } from "./middleware"
|
|
4
|
-
|
|
5
|
-
export class Command<IN extends StandardSchemaV1, OUT extends StandardSchemaV1> implements ICommand<IN, OUT> {
|
|
6
|
-
|
|
7
|
-
public readonly "~type": "kunk:command" = "kunk:command"
|
|
8
|
-
|
|
9
|
-
private constructor(
|
|
10
|
-
public readonly name: string,
|
|
11
|
-
public readonly visibility: "PUBLIC" | "PROTECTED" | "PRIVATE",
|
|
12
|
-
public input: IN,
|
|
13
|
-
public output: OUT,
|
|
14
|
-
public middlewares: IMiddleware[],
|
|
15
|
-
public handler: ICommandHandler<IN, OUT>,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
private static create = (visibility: "PUBLIC" | "PROTECTED" | "PRIVATE") => <IN extends StandardSchemaV1 = StandardSchemaV1, OUT extends StandardSchemaV1 = StandardSchemaV1>(
|
|
19
|
-
setup: {
|
|
20
|
-
name: string,
|
|
21
|
-
input: IN
|
|
22
|
-
output: OUT
|
|
23
|
-
middlewares?: IMiddleware[]
|
|
24
|
-
},
|
|
25
|
-
handler: ICommandHandler<IN, OUT>,
|
|
26
|
-
) => {
|
|
27
|
-
return new Command<IN, OUT>(
|
|
28
|
-
setup.name,
|
|
29
|
-
visibility,
|
|
30
|
-
setup.input,
|
|
31
|
-
setup.output,
|
|
32
|
-
setup.middlewares || [],
|
|
33
|
-
handler
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public static PUBLIC = this.create("PUBLIC")
|
|
38
|
-
public static PROTECTED = this.create("PROTECTED")
|
|
39
|
-
public static PRIVATE = this.create("PRIVATE")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async internalCall(context: IContextCommand<IN, OUT>): Promise<I.Output<OUT>> {
|
|
43
|
-
if (!this.handler) {
|
|
44
|
-
throw SERVER_ERROR.UNEXPECTED("HANDLER_NOT_SET", {
|
|
45
|
-
command: this.name
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
return this.handler(context)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async call(context: IContextCommand<IN, OUT>): Promise<I.Output<OUT>> {
|
|
52
|
-
const caller = () => this.internalCall(context)
|
|
53
|
-
const handler: any = middleware(caller, this.middlewares)
|
|
54
|
-
return handler(context)
|
|
55
|
-
}
|
|
56
|
-
}
|
package/src/configs/+.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./DatabaseConfig"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Config } from "@/utils/config"
|
|
2
|
-
import { T } from "@kunk/model"
|
|
3
|
-
|
|
4
|
-
export const DatabaseConfig = await Config.create({
|
|
5
|
-
name: "database",
|
|
6
|
-
schema: T.object({
|
|
7
|
-
uri: T.string().default("${DATABASE_URI}"),
|
|
8
|
-
idleTimeout: T.number().default(5),
|
|
9
|
-
connectionTimeout: T.number().default(30),
|
|
10
|
-
maxPoolSize: T.number().default(30),
|
|
11
|
-
})
|
|
12
|
-
})
|
package/src/context.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { IService, IError, IContext, IContainer, IContextCommand } from "@/interfaces/+";
|
|
2
|
-
import { ContextData, Database, Generator, Logger } from "@/contracts/+";
|
|
3
|
-
import { DrizzleQuery } from "@/services/+";
|
|
4
|
-
import { SERVER_ERROR } from "@/utils/+";
|
|
5
|
-
import { Command } from "./command";
|
|
6
|
-
import { registry } from "./registry";
|
|
7
|
-
import { Container } from "inversify";
|
|
8
|
-
|
|
9
|
-
// export const registry = new FinalizationRegistry<Context>(async (context) => {
|
|
10
|
-
// console.log(`Finalizing context`);
|
|
11
|
-
// await context.db.commit()
|
|
12
|
-
// });
|
|
13
|
-
|
|
14
|
-
export class Context implements IContext {
|
|
15
|
-
|
|
16
|
-
public readonly gen: Generator;
|
|
17
|
-
public readonly logger: Logger;
|
|
18
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
private readonly container: IContainer,
|
|
21
|
-
public readonly db: Database
|
|
22
|
-
) {
|
|
23
|
-
this.gen = container.get(Generator)
|
|
24
|
-
this.logger = container.get(Logger)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static async create(contextData: ContextData = {}): Promise<Context> {
|
|
28
|
-
const container = new Container({ defaultScope: 'Singleton', parent: registry })
|
|
29
|
-
container.bind(ContextData).toConstantValue(contextData)
|
|
30
|
-
const db = await container.getAsync(Database)
|
|
31
|
-
return new Context(container, db)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public readonly Q = DrizzleQuery
|
|
35
|
-
|
|
36
|
-
execute: IContext["execute"] = async (command: any, input: any) => {
|
|
37
|
-
let _command: Command<any, any> = command
|
|
38
|
-
if (_command instanceof Function) {
|
|
39
|
-
_command = command()
|
|
40
|
-
}
|
|
41
|
-
if (_command instanceof Promise) {
|
|
42
|
-
_command = await _command.then(cmd => cmd.default)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let newInput: any = input
|
|
46
|
-
if (_command.input) {
|
|
47
|
-
const validated = await _command.input["~standard"].validate(input)
|
|
48
|
-
if (validated.issues) {
|
|
49
|
-
throw SERVER_ERROR.SCHEMA_VALIDATION(validated.issues)
|
|
50
|
-
}
|
|
51
|
-
newInput = validated.value
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const context: IContextCommand = {
|
|
55
|
-
...this,
|
|
56
|
-
command: _command,
|
|
57
|
-
input: newInput
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let error: IError | null = null
|
|
61
|
-
// context.logger.info(`started`)
|
|
62
|
-
try {
|
|
63
|
-
const caller = () => _command.call(context as any)
|
|
64
|
-
const handler: any = caller
|
|
65
|
-
return await handler(context, this)
|
|
66
|
-
} catch (err: any) {
|
|
67
|
-
error = SERVER_ERROR.FORWARD(err)
|
|
68
|
-
throw error
|
|
69
|
-
} finally {
|
|
70
|
-
if (error) {
|
|
71
|
-
// context.logger.error(error)
|
|
72
|
-
} else {
|
|
73
|
-
// context.logger.info(`finished`)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
prepare: IContext["prepare"] = (command: any, input: any) => {
|
|
79
|
-
return {
|
|
80
|
-
input,
|
|
81
|
-
execute: () => this.execute(command, input)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get = <T>(service: IService<T>): T => {
|
|
86
|
-
return this.container.get(service)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getAsync = async <T>(service: IService<T>): Promise<T> => {
|
|
90
|
-
return await this.container.getAsync(service)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
[Symbol.dispose] = async (): Promise<void> => {
|
|
94
|
-
await this.db.commit()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
}
|
package/src/contracts/+.ts
DELETED
package/src/contracts/Auth.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
export abstract class Auth {
|
|
3
|
-
|
|
4
|
-
abstract getSession(): Promise<Auth.Session>
|
|
5
|
-
|
|
6
|
-
abstract signInEmail(data: Auth.SignInEmail.Input): Promise<Auth.SignInEmail.Output>
|
|
7
|
-
|
|
8
|
-
abstract signOut(): Promise<void>
|
|
9
|
-
|
|
10
|
-
abstract signUpEmail(data: Auth.SignUpEmail.Input): Promise<Auth.SignUpEmail.Output>
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export namespace Auth {
|
|
15
|
-
|
|
16
|
-
export type User = {
|
|
17
|
-
id: string;
|
|
18
|
-
email: string;
|
|
19
|
-
name: string;
|
|
20
|
-
image: string | null | undefined;
|
|
21
|
-
emailVerified: boolean;
|
|
22
|
-
createdAt: Date;
|
|
23
|
-
updatedAt: Date;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type Session = {
|
|
27
|
-
redirect: boolean;
|
|
28
|
-
token: string;
|
|
29
|
-
url: string | undefined;
|
|
30
|
-
user: User
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type SignUpEmailResponse = {
|
|
34
|
-
redirect: boolean;
|
|
35
|
-
token: string;
|
|
36
|
-
url: string | undefined;
|
|
37
|
-
user: User
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export namespace SignUpEmail {
|
|
41
|
-
|
|
42
|
-
export type Input = {
|
|
43
|
-
email: string;
|
|
44
|
-
password: string;
|
|
45
|
-
name: string;
|
|
46
|
-
rememberMe?: boolean;
|
|
47
|
-
callbackURL?: string;
|
|
48
|
-
image?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type Output = {
|
|
52
|
-
token: string | null;
|
|
53
|
-
user: User;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export namespace SignInEmail {
|
|
59
|
-
|
|
60
|
-
export type Input = {
|
|
61
|
-
email: string;
|
|
62
|
-
password: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type Output = {
|
|
66
|
-
redirect: boolean;
|
|
67
|
-
token: string;
|
|
68
|
-
url: string | undefined;
|
|
69
|
-
user: User;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { InferInsertModel, InferSelectModel, SQL, Table } from "drizzle-orm"
|
|
2
|
-
import type { IndexColumn, PgColumn } from "drizzle-orm/pg-core"
|
|
3
|
-
|
|
4
|
-
export abstract class Database {
|
|
5
|
-
abstract commit(): Promise<void>
|
|
6
|
-
|
|
7
|
-
abstract create<T extends Table, V extends InferInsertModel<T> | InferInsertModel<T>[]>(
|
|
8
|
-
entity: T,
|
|
9
|
-
values: V
|
|
10
|
-
): Promise<
|
|
11
|
-
V extends InferInsertModel<T>[]
|
|
12
|
-
? { [K in keyof V]: NonNullable<InferSelectModel<T>> }
|
|
13
|
-
: NonNullable<InferSelectModel<T>>
|
|
14
|
-
>
|
|
15
|
-
|
|
16
|
-
abstract update<T extends Table>(entity: T,
|
|
17
|
-
id: string,
|
|
18
|
-
values: Partial<InferInsertModel<T>>
|
|
19
|
-
): Promise<InferSelectModel<T>>
|
|
20
|
-
|
|
21
|
-
abstract upsert<T extends Table>(entity: T, options: {
|
|
22
|
-
target: IndexColumn | IndexColumn[],
|
|
23
|
-
values: InferInsertModel<T>
|
|
24
|
-
}): Promise<InferSelectModel<T>>
|
|
25
|
-
|
|
26
|
-
abstract delete<T extends Table>(entity: T, id: string): Promise<void>
|
|
27
|
-
|
|
28
|
-
abstract findMany<T extends Table>(entity: T, options?: {
|
|
29
|
-
where?: SQL,
|
|
30
|
-
limit?: number,
|
|
31
|
-
offset?: number,
|
|
32
|
-
orderBy?: (PgColumn | SQL | SQL.Aliased)[],
|
|
33
|
-
}): Promise<InferSelectModel<T>[]>
|
|
34
|
-
|
|
35
|
-
abstract findOne<T extends Table>(entity: T, options?: {
|
|
36
|
-
where?: SQL,
|
|
37
|
-
}): Promise<InferSelectModel<T> | null>
|
|
38
|
-
|
|
39
|
-
abstract get<T extends Table>(entity: T, id: string): Promise<InferSelectModel<T>>
|
|
40
|
-
}
|
package/src/contracts/Query.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export * from "./configs/+"
|
|
2
|
-
export * from "./contracts/+"
|
|
3
|
-
export * from "./interfaces/+"
|
|
4
|
-
export * from "./services/+"
|
|
5
|
-
export * from "./utils/+"
|
|
6
|
-
export * from "./context"
|
|
7
|
-
export * from "./command"
|
|
8
|
-
export * from "./middleware"
|
|
9
|
-
export * from "./server"
|
|
10
|
-
export * from "./registry"
|
|
11
|
-
export * from "./setup"
|
package/src/interfaces/+.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export type { I, ICommand, StandardSchemaV1, IError } from "@kunk/api"
|
|
2
|
-
|
|
3
|
-
export * from "./IApplication"
|
|
4
|
-
export * from "./IContext"
|
|
5
|
-
export * from "./IContextCommand"
|
|
6
|
-
export * from "./IContextHandler"
|
|
7
|
-
export * from "./IMiddleware"
|
|
8
|
-
export * from "./IService"
|
|
9
|
-
export * from "./IContainer"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type { Container as IContainer } from "inversify";
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { I, ICommand, StandardSchemaV1 } from "@kunk/api";
|
|
2
|
-
import type { Generator, Logger, Database, Query } from "@/contracts/+";
|
|
3
|
-
import type { IService } from "./IService";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type Executor = {
|
|
7
|
-
<T extends Promise<{ default: ICommand<any, any> }>>(command: () => T, input: I.Input<Awaited<T>['default']>): Promise<I.Output<Awaited<T>['default']>>
|
|
8
|
-
<IN extends StandardSchemaV1, OUT extends StandardSchemaV1>(command: ICommand<IN, OUT>, input: I.Input<IN>): Promise<I.Output<OUT>>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type Prepare = {
|
|
12
|
-
<T extends Promise<{ default: ICommand<any, any> }>>(command: () => T, input: I.Input<Awaited<T>['default']>): {
|
|
13
|
-
input: I.Input<Awaited<T>['default']>
|
|
14
|
-
execute: () => Promise<I.Output<Awaited<T>['default']>>
|
|
15
|
-
}
|
|
16
|
-
<IN extends StandardSchemaV1, OUT extends StandardSchemaV1>(command: ICommand<IN, OUT>, input: I.Input<IN>): {
|
|
17
|
-
input: I.Input<IN>
|
|
18
|
-
execute: () => Promise<I.Output<OUT>>
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface IContext {
|
|
23
|
-
db: Database
|
|
24
|
-
Q: Query
|
|
25
|
-
gen: Generator
|
|
26
|
-
logger: Logger
|
|
27
|
-
prepare: Prepare
|
|
28
|
-
execute: Executor
|
|
29
|
-
get: <T>(serviceIdentifier: IService<T>) => T
|
|
30
|
-
getAsync: <T>(serviceIdentifier: IService<T>) => Promise<T>
|
|
31
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { I, ICommand, StandardSchemaV1 } from "@kunk/api"
|
|
2
|
-
import type { IContext } from "./IContext"
|
|
3
|
-
|
|
4
|
-
export interface IContextCommand<IN extends StandardSchemaV1 = StandardSchemaV1, OUT extends StandardSchemaV1 = StandardSchemaV1> extends IContext {
|
|
5
|
-
readonly command: ICommand<IN, OUT>
|
|
6
|
-
readonly input: I.Output<IN>
|
|
7
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { I, StandardSchemaV1 } from "@kunk/api"
|
|
2
|
-
import type { IContextCommand } from "./IContextCommand"
|
|
3
|
-
|
|
4
|
-
export interface ICommandHandler<IN extends StandardSchemaV1, OUT extends StandardSchemaV1> {
|
|
5
|
-
(context: IContextCommand<IN, OUT>): Promise<I.Output<OUT>>
|
|
6
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type { ServiceIdentifier as IService } from "inversify";
|
package/src/middleware.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { IMiddleware } from "@/interfaces/+"
|
|
2
|
-
|
|
3
|
-
export function middleware<T extends (...args: any[]) => any>(
|
|
4
|
-
fn: T,
|
|
5
|
-
middlewares: IMiddleware[]
|
|
6
|
-
): T {
|
|
7
|
-
return middlewares.reduceRight((next: T, mw: IMiddleware) => {
|
|
8
|
-
return ((ctx: any) => mw({
|
|
9
|
-
...ctx,
|
|
10
|
-
next,
|
|
11
|
-
})) as any
|
|
12
|
-
}, fn);
|
|
13
|
-
}
|
package/src/registry.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Generator } from "@kunk/api";
|
|
2
|
-
import { Container } from "inversify";
|
|
3
|
-
import { DrizzleQuery } from "./services/DrizzleQuery";
|
|
4
|
-
|
|
5
|
-
export const registry = new Container({ defaultScope: "Singleton" })
|
|
6
|
-
|
|
7
|
-
export function gen() {
|
|
8
|
-
return registry.get(Generator)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const Q = DrizzleQuery
|
package/src/server.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import "./setup"
|
|
2
|
-
import type { ICommand } from "@/interfaces/+"
|
|
3
|
-
import openapi, { fromTypes } from "@elysiajs/openapi"
|
|
4
|
-
import Elysia, { ValidationError } from "elysia"
|
|
5
|
-
import { Throwable } from "@kunk/api"
|
|
6
|
-
import { Context } from "./context"
|
|
7
|
-
import { T } from "@kunk/model"
|
|
8
|
-
|
|
9
|
-
export const Server = new Elysia()
|
|
10
|
-
.use(openapi({
|
|
11
|
-
references: fromTypes(),
|
|
12
|
-
mapJsonSchema: {
|
|
13
|
-
zod: T.toJSONSchema
|
|
14
|
-
},
|
|
15
|
-
documentation: {
|
|
16
|
-
servers: [
|
|
17
|
-
{
|
|
18
|
-
url: "/",
|
|
19
|
-
description: "The base URL of the API",
|
|
20
|
-
variables: {
|
|
21
|
-
tenant_id: {
|
|
22
|
-
default: "test"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}))
|
|
29
|
-
.onError(({ error }) => {
|
|
30
|
-
if (Throwable.is(error)) {
|
|
31
|
-
return Response.json(error)
|
|
32
|
-
}
|
|
33
|
-
if (error instanceof T.ModelError) {
|
|
34
|
-
return Response.json(
|
|
35
|
-
Throwable.forward(error)
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
if (error instanceof ValidationError) {
|
|
39
|
-
return Response.json(error)
|
|
40
|
-
}
|
|
41
|
-
if (error instanceof Error) {
|
|
42
|
-
return Response.json(
|
|
43
|
-
error
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
return Response.json(
|
|
47
|
-
Throwable.forward(error)
|
|
48
|
-
)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export async function route<K extends string, CMD extends ICommand<any, any>>(path: K, commandImport: Promise<{ default: CMD }>) {
|
|
54
|
-
const splited = path.split("/")
|
|
55
|
-
const tag = splited.slice(0, -1).join(".");
|
|
56
|
-
const summary = splited.pop()!;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const command = (await commandImport).default
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let input: CMD["input"] = command.input?.type === 'void' ? null : command.input
|
|
63
|
-
let output: CMD["output"] = command.output?.type === 'void' ? null : command.output
|
|
64
|
-
|
|
65
|
-
console.info("command loaded:", path);
|
|
66
|
-
|
|
67
|
-
const elysia = new Elysia()
|
|
68
|
-
|
|
69
|
-
return elysia.post(path, async ({ headers, body }) => {
|
|
70
|
-
const tenantId = headers["x-tenant-id"];
|
|
71
|
-
using ctx = await Context.create({
|
|
72
|
-
tenantId: tenantId!
|
|
73
|
-
});
|
|
74
|
-
const result = await ctx.execute(command, body);
|
|
75
|
-
return Response.json(result);
|
|
76
|
-
}, {
|
|
77
|
-
body: input,
|
|
78
|
-
response: output,
|
|
79
|
-
headers: T.object({
|
|
80
|
-
"x-tenant-id": T.string().default("{{tenant_id}}").optional(),
|
|
81
|
-
}),
|
|
82
|
-
detail: {
|
|
83
|
-
summary,
|
|
84
|
-
},
|
|
85
|
-
tags: [tag]
|
|
86
|
-
});
|
|
87
|
-
}
|
package/src/services/+.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Generator } from "@kunk/api"
|
|
2
|
-
|
|
3
|
-
export class BunGenerator extends Generator {
|
|
4
|
-
|
|
5
|
-
override get id() {
|
|
6
|
-
return Bun.randomUUIDv7()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
override code(length: number) {
|
|
10
|
-
return Bun.randomUUIDv7().replace(/-/g, '').slice(0, length)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { eq, type InferInsertModel, type InferSelectModel, type SQL, type Table } from "drizzle-orm";
|
|
2
|
-
import { drizzle } from "drizzle-orm/bun-sql";
|
|
3
|
-
import type { BunSQLDatabase } from "drizzle-orm/bun-sql/postgres/driver";
|
|
4
|
-
import { SERVER_ERROR } from "@/utils/+";
|
|
5
|
-
import { Database } from "@/contracts/+";
|
|
6
|
-
import type { ContextData } from "@kunk/api";
|
|
7
|
-
import type { IndexColumn, PgColumn } from "drizzle-orm/pg-core";
|
|
8
|
-
|
|
9
|
-
export class DizzleDatabase extends Database {
|
|
10
|
-
|
|
11
|
-
private released: boolean = false
|
|
12
|
-
|
|
13
|
-
private constructor(
|
|
14
|
-
private readonly sql: BunSQLDatabase,
|
|
15
|
-
private readonly session: Bun.ReservedSQL
|
|
16
|
-
) {
|
|
17
|
-
super()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static async create(sql: Bun.SQL, contextData: ContextData): Promise<DizzleDatabase> {
|
|
21
|
-
const session = await sql.reserve()
|
|
22
|
-
if (contextData.tenantId) {
|
|
23
|
-
await session`SELECT set_config('app.tenant_id', ${contextData.tenantId}, false);`
|
|
24
|
-
}
|
|
25
|
-
return new DizzleDatabase(drizzle({ client: session }), session)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
override async create<T extends Table, V extends InferInsertModel<T> | InferInsertModel<T>[]>(
|
|
29
|
-
entity: T,
|
|
30
|
-
values: V
|
|
31
|
-
): Promise<
|
|
32
|
-
V extends InferInsertModel<T>[]
|
|
33
|
-
? { [K in keyof V]: NonNullable<InferSelectModel<T>> }
|
|
34
|
-
: NonNullable<InferSelectModel<T>>
|
|
35
|
-
> {
|
|
36
|
-
const table = entity as any
|
|
37
|
-
const result = await this.sql.insert(table).values(values).returning()
|
|
38
|
-
|
|
39
|
-
const length = Array.isArray(values) ? values.length : 1
|
|
40
|
-
if (result.length !== length) {
|
|
41
|
-
throw SERVER_ERROR.UNEXPECTED("FAILED_TO_INSERT_ENTITY", {})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (Array.isArray(values)) {
|
|
45
|
-
return result as any
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return result[0] as any
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async update<T extends Table>(
|
|
52
|
-
entity: T,
|
|
53
|
-
id: string,
|
|
54
|
-
values: Partial<InferInsertModel<T>>
|
|
55
|
-
): Promise<InferSelectModel<T>> {
|
|
56
|
-
const table = entity as any
|
|
57
|
-
const [result] = await this.sql.update(table)
|
|
58
|
-
.set(values)
|
|
59
|
-
.where(eq(table.id, id))
|
|
60
|
-
.returning()
|
|
61
|
-
|
|
62
|
-
if (!result) {
|
|
63
|
-
throw SERVER_ERROR.UNEXPECTED("FAILED_TO_UPDATE_ENTITY", { })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return result as InferSelectModel<T>
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async upsert<T extends Table>(entity: T, options: {
|
|
70
|
-
target: IndexColumn | IndexColumn[],
|
|
71
|
-
values: InferInsertModel<T>
|
|
72
|
-
}): Promise<InferSelectModel<T>> {
|
|
73
|
-
const table = entity as any
|
|
74
|
-
const [result] = await this.sql.insert(table).values(options.values).onConflictDoUpdate({
|
|
75
|
-
target: options.target,
|
|
76
|
-
set: options.values,
|
|
77
|
-
}).returning()
|
|
78
|
-
|
|
79
|
-
if (!result) {
|
|
80
|
-
throw SERVER_ERROR.UNEXPECTED("FAILED_TO_UPSERT_ENTITY", {})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return result as InferSelectModel<T>
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async findMany<T extends Table>(entity: T, options?: {
|
|
87
|
-
where?: SQL,
|
|
88
|
-
limit?: number,
|
|
89
|
-
offset?: number,
|
|
90
|
-
orderBy?: (PgColumn | SQL | SQL.Aliased)[],
|
|
91
|
-
}): Promise<InferSelectModel<T>[]> {
|
|
92
|
-
const table = entity as any
|
|
93
|
-
const result = await this.sql.select().from(table)
|
|
94
|
-
.where(options?.where)
|
|
95
|
-
.limit(options?.limit ?? 1000)
|
|
96
|
-
.offset(options?.offset ?? 0)
|
|
97
|
-
.orderBy(...(options?.orderBy ?? []))
|
|
98
|
-
.execute()
|
|
99
|
-
|
|
100
|
-
if (!result) {
|
|
101
|
-
throw SERVER_ERROR.UNEXPECTED("FAILED_TO_GET_MANY_ENTITIES", {})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return result as InferSelectModel<T>[]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async findOne<T extends Table>(entity: T, options?: {
|
|
108
|
-
where?: SQL,
|
|
109
|
-
}): Promise<InferSelectModel<T> | null> {
|
|
110
|
-
const table = entity as any
|
|
111
|
-
const [result] = await this.sql.select().from(table).where(options?.where).limit(1).execute()
|
|
112
|
-
|
|
113
|
-
if (!result) {
|
|
114
|
-
return null
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return result as InferSelectModel<T>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async get<T extends Table>(entity: T, id: string): Promise<InferSelectModel<T>> {
|
|
121
|
-
const table = entity as any
|
|
122
|
-
const [result] = await this.sql.select().from(table).where(eq(table.id, id)).limit(1).execute()
|
|
123
|
-
|
|
124
|
-
if (!result) {
|
|
125
|
-
throw SERVER_ERROR.GET_ENTITY_NOT_FOUND(table, id)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return result as InferSelectModel<T>
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async delete<T extends Table>(entity: T, id: string): Promise<void> {
|
|
132
|
-
const table = entity as any
|
|
133
|
-
await this.sql.delete(table).where(eq(table.id, id))
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async commit(): Promise<void> {
|
|
137
|
-
if (!this.released) {
|
|
138
|
-
this.released = true
|
|
139
|
-
this.session.release()
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * as DrizzleQuery from "drizzle-orm/sql/expressions";
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Logger } from "@kunk/api"
|
|
2
|
-
import pino from "pino"
|
|
3
|
-
|
|
4
|
-
const rootLogger = pino({
|
|
5
|
-
name: 'app',
|
|
6
|
-
transport: {
|
|
7
|
-
target: 'pino-pretty',
|
|
8
|
-
options: {
|
|
9
|
-
colorize: true,
|
|
10
|
-
translateTime: 'SYS:standard',
|
|
11
|
-
ignore: 'pid,hostname'
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
export class PinoLogger implements Logger {
|
|
17
|
-
|
|
18
|
-
constructor(private logger: pino.Logger = rootLogger) {}
|
|
19
|
-
|
|
20
|
-
info(message: string): void {
|
|
21
|
-
this.logger.info(message)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
warn(message: string): void {
|
|
25
|
-
this.logger.warn(message)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
error(error: any): void {
|
|
29
|
-
this.logger.error(error)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
fork(name: string): Logger {
|
|
33
|
-
return new PinoLogger(this.logger.child({ name }))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
}
|
package/src/setup.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import * as Contracts from "@/contracts/+";
|
|
2
|
-
import * as Services from "@/services/+";
|
|
3
|
-
import { registry } from "@/registry";
|
|
4
|
-
import { DizzleDatabase } from "@/services/+";
|
|
5
|
-
import { DatabaseConfig } from "@/configs/+";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
registry.bind(Contracts.Logger).to(Services.PinoLogger)
|
|
9
|
-
registry.bind(Contracts.Generator).to(Services.BunGenerator)
|
|
10
|
-
|
|
11
|
-
registry.bind(Bun.SQL).toDynamicValue(() => {
|
|
12
|
-
const config = DatabaseConfig.get()
|
|
13
|
-
return new Bun.SQL({
|
|
14
|
-
url: config.uri,
|
|
15
|
-
max: config.maxPoolSize,
|
|
16
|
-
idleTimeout: config.idleTimeout,
|
|
17
|
-
connectionTimeout: config.connectionTimeout
|
|
18
|
-
})
|
|
19
|
-
}).inSingletonScope()
|
|
20
|
-
|
|
21
|
-
registry.bind(Contracts.Database).toDynamicValue(async (container) => {
|
|
22
|
-
return await DizzleDatabase.create(
|
|
23
|
-
container.get(Bun.SQL),
|
|
24
|
-
container.get(Contracts.ContextData)
|
|
25
|
-
)
|
|
26
|
-
}).inRequestScope()
|
package/src/utils/+.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
2
|
-
import { Throwable, type IThrowable } from "@kunk/api"
|
|
3
|
-
import { getTableUniqueName, Table } from "drizzle-orm"
|
|
4
|
-
|
|
5
|
-
export namespace SERVER_ERROR {
|
|
6
|
-
|
|
7
|
-
export function RESOURCE_ID_NOT_FOUND(resource: string, id: string) {
|
|
8
|
-
return Throwable.create("RESOURCE_ID_NOT_FOUND", 404, { resource, id })
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function RESOURCE_NOT_FOUND(resource: string) {
|
|
12
|
-
return Throwable.create("RESOURCE_NOT_FOUND", 404, { resource })
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function BAD_REQUEST(type: string, args: Record<string, any>) {
|
|
16
|
-
return Throwable.create(type, 400, args)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function RESOURCE_ID_ALREADY_EXISTS(resource: string, id: string) {
|
|
20
|
-
return Throwable.create("RESOURCE_ID_ALREADY_EXISTS", 400, { resource, id })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function VALIDATION(...issues: IThrowable[]) {
|
|
24
|
-
return Throwable.create("VALIDATION_ERROR", 422, { issues })
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function SCHEMA_VALIDATION(issues: readonly StandardSchemaV1.Issue[]) {
|
|
28
|
-
return Throwable.create("SCHEMA_VALIDATION_ERROR", 422, { issues })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function UNEXPECTED(type: string, args: Record<string, any>) {
|
|
32
|
-
return Throwable.create(type, 500, args)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const FORWARD = Throwable.forward
|
|
36
|
-
|
|
37
|
-
export function GET_ENTITY_NOT_FOUND<T extends Table>(entity: T, id: string) {
|
|
38
|
-
return SERVER_ERROR.RESOURCE_ID_NOT_FOUND(getTableUniqueName(entity), id)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { SERVER_ERROR } from "./SERVER_ERROR";
|
|
2
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
import type { I } from "@kunk/api";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
|
|
6
|
-
function interpolateEnvVars<T>(obj: T): T {
|
|
7
|
-
if (typeof obj === 'string') {
|
|
8
|
-
return obj.replace(
|
|
9
|
-
/\${([^:-]+)(?:-([^}]+))?}/g,
|
|
10
|
-
(_, key, defaultValue) => Bun.env[key] || defaultValue || ''
|
|
11
|
-
) as T;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (typeof obj === 'object' && obj !== null) {
|
|
15
|
-
const result = (Array.isArray(obj) ? [] : {}) as T;
|
|
16
|
-
for (const key in obj) {
|
|
17
|
-
result[key] = interpolateEnvVars(obj[key]);
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return obj;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function loadFromFile<T extends StandardSchemaV1>(name: string, schema: T): Promise<I.Output<T>> {
|
|
26
|
-
let rawConfig = {}
|
|
27
|
-
const filePath = path.resolve(process.cwd(), `./config/${name}.yaml`)
|
|
28
|
-
|
|
29
|
-
if (await Bun.file(filePath).exists()) {
|
|
30
|
-
rawConfig = await import(filePath).then(module => module.default?.[Bun.env.NODE_ENV || "local"]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const validated = await schema["~standard"].validate(rawConfig);
|
|
34
|
-
if (validated.issues) {
|
|
35
|
-
throw SERVER_ERROR.SCHEMA_VALIDATION(validated.issues)
|
|
36
|
-
}
|
|
37
|
-
return interpolateEnvVars(validated.value) as I.Output<T>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export class Config<T extends StandardSchemaV1> {
|
|
42
|
-
|
|
43
|
-
private constructor(
|
|
44
|
-
private readonly schema: T,
|
|
45
|
-
private values: I.Output<T>
|
|
46
|
-
) {}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
public get() {
|
|
50
|
-
return this.values;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public async set(values: I.Input<T>): Promise<void> {
|
|
54
|
-
const validated = await this.schema["~standard"].validate(values);
|
|
55
|
-
if (validated.issues) {
|
|
56
|
-
throw SERVER_ERROR.SCHEMA_VALIDATION(validated.issues)
|
|
57
|
-
}
|
|
58
|
-
this.values = validated.value as I.Output<T>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static async create<T extends StandardSchemaV1>(options: { name: string, schema: T }) {
|
|
62
|
-
const values = await loadFromFile(options.name, options.schema);
|
|
63
|
-
return new Config(options.schema, values as I.Output<T>);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public static set<T extends StandardSchemaV1>(config: Config<T>, values: I.Input<T>) {
|
|
67
|
-
config.set(values);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"rootDir": ".",
|
|
4
|
-
"baseUrl": ".",
|
|
5
|
-
"paths": {
|
|
6
|
-
"@/*": ["./src/*"]
|
|
7
|
-
},
|
|
8
|
-
"strict": true,
|
|
9
|
-
"module": "Preserve",
|
|
10
|
-
"target": "ESNext"
|
|
11
|
-
},
|
|
12
|
-
"include": [
|
|
13
|
-
"src/**/*.ts"
|
|
14
|
-
],
|
|
15
|
-
"exclude": [
|
|
16
|
-
"node_modules",
|
|
17
|
-
"dist"
|
|
18
|
-
]
|
|
19
|
-
}
|