@kunk/server 3.0.2 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- }
@@ -1,5 +0,0 @@
1
- export { Logger, Generator, Validator, ContextData } from "@kunk/api"
2
-
3
- export * from "./Auth"
4
- export * from "./Database"
5
- export * from "./Query"
@@ -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
- }
@@ -1,3 +0,0 @@
1
- import * as DrizzleQuery from "drizzle-orm/sql/expressions";
2
-
3
- export type Query = typeof DrizzleQuery;
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"
@@ -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,6 +0,0 @@
1
- import type { BindToFluentSyntax } from "inversify";
2
- import type { IService } from "./IService";
3
-
4
- export interface IApplication {
5
- bind<T>(serviceIdentifier: IService<T>): BindToFluentSyntax<T>
6
- }
@@ -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,6 +0,0 @@
1
- import type { Container } from "inversify";
2
-
3
- export type IMiddleware = (ctx: {
4
- next: any,
5
- container: Container,
6
- }) => any;
@@ -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,4 +0,0 @@
1
- export * from "./BunGenerator"
2
- export * from "./PinoLogger"
3
- export * from "./DizzleDatabase"
4
- export * from "./DrizzleQuery"
@@ -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,2 +0,0 @@
1
- export * from "./SERVER_ERROR"
2
- export * from "./config"