@bejibun/core 0.1.21 → 0.1.22
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/dist/bases/BaseController.d.ts +10 -0
- package/dist/bases/BaseController.js +126 -0
- package/dist/bases/BaseModel.d.ts +33 -0
- package/dist/bases/BaseModel.js +68 -0
- package/dist/bases/BaseValidator.d.ts +5 -0
- package/dist/bases/BaseValidator.js +7 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +11 -0
- package/dist/builders/ChalkBuilder.d.ts +14 -0
- package/dist/builders/ChalkBuilder.js +48 -0
- package/dist/builders/EnumBuilder.d.ts +4 -0
- package/dist/builders/ResponseBuilder.d.ts +11 -0
- package/dist/builders/ResponseBuilder.js +41 -0
- package/dist/builders/RouterBuilder.d.ts +19 -0
- package/dist/builders/RouterBuilder.js +128 -0
- package/dist/config/cors.d.ts +2 -0
- package/dist/config/cors.js +9 -0
- package/dist/config/database.d.ts +3 -0
- package/dist/config/database.js +24 -0
- package/dist/enums/CorsHeaderEnum.d.ts +8 -0
- package/dist/enums/CorsHeaderEnum.js +9 -0
- package/dist/enums/HttpMethodEnum.d.ts +12 -0
- package/dist/enums/HttpMethodEnum.js +13 -0
- package/dist/exceptions/ModelNotFoundException.d.ts +4 -0
- package/dist/exceptions/ModelNotFoundException.js +11 -0
- package/dist/exceptions/RouterInvalidException.d.ts +4 -0
- package/dist/exceptions/RouterInvalidException.js +11 -0
- package/dist/exceptions/ValidatorException.d.ts +4 -0
- package/dist/exceptions/ValidatorException.js +11 -0
- package/dist/facades/Chalk.d.ts +4 -0
- package/dist/facades/Chalk.js +6 -0
- package/dist/facades/Enum.d.ts +2 -0
- package/dist/facades/Response.d.ts +6 -0
- package/dist/facades/Response.js +12 -0
- package/dist/facades/Router.d.ts +21 -0
- package/dist/facades/Router.js +66 -0
- package/dist/facades/SoftDeletes.d.ts +11 -0
- package/dist/facades/SoftDeletes.js +50 -0
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -9
- package/dist/types/index.d.ts +2 -0
- package/dist/types/middleware.d.ts +7 -0
- package/dist/types/router.d.ts +5 -0
- package/dist/types/validator.d.ts +3 -0
- package/dist/types/vine.d.ts +13 -0
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.js +30 -1
- package/dist/utils/vine.d.ts +2 -0
- package/dist/utils/vine.js +2 -0
- package/dist/utils/vines/exists.d.ts +1 -0
- package/dist/utils/vines/exists.js +28 -0
- package/dist/utils/vines/unique.d.ts +1 -0
- package/dist/utils/vines/unique.js +28 -0
- package/package.json +35 -10
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VineValidator } from "@vinejs/vine";
|
|
2
|
+
import { BunRequest } from "bun";
|
|
3
|
+
import Response from "../facades/Response";
|
|
4
|
+
export default class BaseController {
|
|
5
|
+
parse(request: BunRequest): Promise<Record<string, any>>;
|
|
6
|
+
get response(): typeof Response;
|
|
7
|
+
validate(validator: VineValidator<any, Record<string, any> | undefined>, body: Record<string, any>): Promise<any>;
|
|
8
|
+
private serialize;
|
|
9
|
+
private parseForm;
|
|
10
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import ValidatorException from "../exceptions/ValidatorException";
|
|
3
|
+
import Response from "../facades/Response";
|
|
4
|
+
import { defineValue, isEmpty, isNotEmpty } from "../utils/utils";
|
|
5
|
+
export default class BaseController {
|
|
6
|
+
async parse(request) {
|
|
7
|
+
const contentType = defineValue(request.headers.get("content-type"), "");
|
|
8
|
+
const formData = new FormData();
|
|
9
|
+
try {
|
|
10
|
+
if (contentType.includes("application/json"))
|
|
11
|
+
return await request.json();
|
|
12
|
+
for (const [key, value] of Object.entries(request.params)) {
|
|
13
|
+
formData.append(key, value);
|
|
14
|
+
}
|
|
15
|
+
const url = new URL(request.url);
|
|
16
|
+
for (const [key, value] of url.searchParams) {
|
|
17
|
+
formData.append(key, value);
|
|
18
|
+
}
|
|
19
|
+
if (contentType.includes("multipart/form-data") ||
|
|
20
|
+
contentType.includes("application/x-www-form-urlencoded")) {
|
|
21
|
+
const body = await request.formData();
|
|
22
|
+
for (const [key, value] of body) {
|
|
23
|
+
formData.append(key, value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const text = await request.text();
|
|
27
|
+
if (isNotEmpty(text))
|
|
28
|
+
formData.append("text", text);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// do nothing
|
|
32
|
+
}
|
|
33
|
+
return this.parseForm(formData);
|
|
34
|
+
}
|
|
35
|
+
get response() {
|
|
36
|
+
return Response;
|
|
37
|
+
}
|
|
38
|
+
async validate(validator, body) {
|
|
39
|
+
try {
|
|
40
|
+
return await validator.validate(body);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new ValidatorException(error.messages[0].message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
serialize(data) {
|
|
47
|
+
if (Array.isArray(data))
|
|
48
|
+
return data.map((value) => this.serialize(value));
|
|
49
|
+
if (data === null || data === undefined)
|
|
50
|
+
return null;
|
|
51
|
+
if (data instanceof DateTime)
|
|
52
|
+
return data.isValid ? data.toISO() : null;
|
|
53
|
+
if (data instanceof Date)
|
|
54
|
+
return Number.isNaN(data.getTime()) ? null : data.toISOString();
|
|
55
|
+
if (typeof data === "object" && !(data instanceof File)) {
|
|
56
|
+
if (Object.keys(data).length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const nested = {};
|
|
59
|
+
Object.keys(data).forEach((key) => {
|
|
60
|
+
nested[key] = this.serialize(data[key]);
|
|
61
|
+
});
|
|
62
|
+
return nested;
|
|
63
|
+
}
|
|
64
|
+
if (typeof data === "string") {
|
|
65
|
+
const trimmed = data.trim();
|
|
66
|
+
if (trimmed === "")
|
|
67
|
+
return null;
|
|
68
|
+
if (trimmed === "true")
|
|
69
|
+
return true;
|
|
70
|
+
if (trimmed === "false")
|
|
71
|
+
return false;
|
|
72
|
+
const numeric = Number(trimmed);
|
|
73
|
+
if (!Number.isNaN(numeric) && trimmed === numeric.toString())
|
|
74
|
+
return numeric;
|
|
75
|
+
return trimmed;
|
|
76
|
+
}
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
parseForm(formData) {
|
|
80
|
+
const result = {};
|
|
81
|
+
for (const [key, value] of formData.entries()) {
|
|
82
|
+
const keys = key.replace(/]/g, "").split("[");
|
|
83
|
+
let current = result;
|
|
84
|
+
for (let i = 0; i < keys.length; i++) {
|
|
85
|
+
const part = keys[i];
|
|
86
|
+
const nextPart = keys[i + 1];
|
|
87
|
+
if (i === keys.length - 1) {
|
|
88
|
+
let convertedValue;
|
|
89
|
+
if (value instanceof File) {
|
|
90
|
+
convertedValue = value;
|
|
91
|
+
}
|
|
92
|
+
else if (value.trim() === "") {
|
|
93
|
+
convertedValue = null;
|
|
94
|
+
}
|
|
95
|
+
else if (Number.isNaN(value)) {
|
|
96
|
+
convertedValue = Number(value);
|
|
97
|
+
}
|
|
98
|
+
else if (value === "true" || value === "false") {
|
|
99
|
+
convertedValue = value === "true";
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
try {
|
|
103
|
+
convertedValue = JSON.parse(value);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
convertedValue = value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (current[part] === undefined)
|
|
110
|
+
current[part] = convertedValue;
|
|
111
|
+
else if (Array.isArray(current[part]))
|
|
112
|
+
current[part].push(convertedValue);
|
|
113
|
+
else
|
|
114
|
+
current[part] = [current[part], convertedValue];
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const isArrayIndex = /^\d+$/.test(nextPart);
|
|
118
|
+
if (isEmpty(current[part]))
|
|
119
|
+
current[part] = isArrayIndex ? [] : {};
|
|
120
|
+
current = current[part];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { Constructor, Model, ModelOptions, PartialModelObject, QueryBuilder, QueryBuilderType, QueryContext, TransactionOrKnex } from "objection";
|
|
3
|
+
import SoftDeletes from "../facades/SoftDeletes";
|
|
4
|
+
export interface BaseColumns {
|
|
5
|
+
id: bigint | number;
|
|
6
|
+
created_at: DateTime | string;
|
|
7
|
+
updated_at: DateTime | string;
|
|
8
|
+
deleted_at: DateTime | string | null;
|
|
9
|
+
}
|
|
10
|
+
declare class BunQueryBuilder<M extends Model, R = M[]> extends SoftDeletes<M, R> {
|
|
11
|
+
update(payload: PartialModelObject<M>): Promise<QueryBuilder<M, R>>;
|
|
12
|
+
}
|
|
13
|
+
export default class BaseModel extends Model implements BaseColumns {
|
|
14
|
+
static tableName: string;
|
|
15
|
+
static idColumn: string;
|
|
16
|
+
static deletedColumn: string;
|
|
17
|
+
static QueryBuilder: typeof BunQueryBuilder;
|
|
18
|
+
id: number | bigint;
|
|
19
|
+
created_at: DateTime | string;
|
|
20
|
+
updated_at: DateTime | string;
|
|
21
|
+
deleted_at: DateTime | string | null;
|
|
22
|
+
static get namespace(): string;
|
|
23
|
+
$beforeInsert(queryContext: QueryContext): void;
|
|
24
|
+
$beforeUpdate(opt: ModelOptions, queryContext: QueryContext): void;
|
|
25
|
+
static query<T extends Model>(this: Constructor<T>, trxOrKnex?: TransactionOrKnex): QueryBuilderType<T>;
|
|
26
|
+
static withTrashed<T extends Model>(this: T): QueryBuilderType<T>;
|
|
27
|
+
static onlyTrashed<T extends Model>(this: T): QueryBuilderType<T>;
|
|
28
|
+
static all<T extends Model>(this: T): QueryBuilderType<T>;
|
|
29
|
+
static create<T extends Model>(this: T, payload: Record<string, any>): QueryBuilderType<T>;
|
|
30
|
+
static find<T extends Model>(this: T, id: bigint | number | string): QueryBuilderType<T>;
|
|
31
|
+
static findOrFail<T extends Model>(this: T, id: bigint | number | string): Promise<T>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { Model } from "objection";
|
|
3
|
+
import { relative, sep } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import ModelNotFoundException from "../exceptions/ModelNotFoundException";
|
|
6
|
+
import SoftDeletes from "../facades/SoftDeletes";
|
|
7
|
+
import Str from "../facades/Str";
|
|
8
|
+
import { defineValue, isEmpty } from "../utils/utils";
|
|
9
|
+
class BunQueryBuilder extends SoftDeletes {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
async update(payload) {
|
|
12
|
+
const cloneQuery = this.clone();
|
|
13
|
+
const beforeRows = await cloneQuery;
|
|
14
|
+
if (isEmpty(beforeRows))
|
|
15
|
+
return defineValue(beforeRows);
|
|
16
|
+
await super.update(payload);
|
|
17
|
+
return cloneQuery;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
class BaseModel extends Model {
|
|
22
|
+
static get namespace() {
|
|
23
|
+
const filePath = fileURLToPath(import.meta.url);
|
|
24
|
+
const appRoot = process.cwd();
|
|
25
|
+
const rel = relative(appRoot, filePath);
|
|
26
|
+
const withoutExt = rel.replace(/\.[tj]s$/, "");
|
|
27
|
+
const namespaces = withoutExt.split(sep);
|
|
28
|
+
namespaces.pop();
|
|
29
|
+
namespaces.push(this.name);
|
|
30
|
+
return namespaces.map(part => Str.toPascalCase(part)).join("/");
|
|
31
|
+
}
|
|
32
|
+
$beforeInsert(queryContext) {
|
|
33
|
+
const now = DateTime.now();
|
|
34
|
+
this.created_at = now;
|
|
35
|
+
this.updated_at = now;
|
|
36
|
+
}
|
|
37
|
+
$beforeUpdate(opt, queryContext) {
|
|
38
|
+
this.updated_at = DateTime.now();
|
|
39
|
+
}
|
|
40
|
+
static query(trxOrKnex) {
|
|
41
|
+
return super.query(trxOrKnex);
|
|
42
|
+
}
|
|
43
|
+
;
|
|
44
|
+
static withTrashed() {
|
|
45
|
+
return this.query().withTrashed();
|
|
46
|
+
}
|
|
47
|
+
static onlyTrashed() {
|
|
48
|
+
return this.query().onlyTrashed();
|
|
49
|
+
}
|
|
50
|
+
static all() {
|
|
51
|
+
return this.query().select();
|
|
52
|
+
}
|
|
53
|
+
static create(payload) {
|
|
54
|
+
return this.query().insert(payload);
|
|
55
|
+
}
|
|
56
|
+
static find(id) {
|
|
57
|
+
return this.query().findById(id);
|
|
58
|
+
}
|
|
59
|
+
static async findOrFail(id) {
|
|
60
|
+
const result = await this.find(id);
|
|
61
|
+
if (isEmpty(result))
|
|
62
|
+
throw new ModelNotFoundException(`[ModelNotFoundException]: No query results for model [${this.namespace}] [${id}].`);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
BaseModel.deletedColumn = "deleted_at";
|
|
67
|
+
BaseModel.QueryBuilder = BunQueryBuilder;
|
|
68
|
+
export default BaseModel;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import BaseModel from "./bases/BaseModel";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import knex from "knex";
|
|
4
|
+
import path from "path";
|
|
5
|
+
const configPath = path.resolve(process.cwd(), "config/database.ts");
|
|
6
|
+
let config;
|
|
7
|
+
if (fs.existsSync(configPath))
|
|
8
|
+
config = require(configPath).default;
|
|
9
|
+
else
|
|
10
|
+
config = require("./config/database").default;
|
|
11
|
+
BaseModel.knex(knex(config));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class ChalkBuilder {
|
|
2
|
+
protected value: string;
|
|
3
|
+
protected isNewLine: boolean;
|
|
4
|
+
protected clk: any;
|
|
5
|
+
constructor();
|
|
6
|
+
setValue(value: string): ChalkBuilder;
|
|
7
|
+
inline(): ChalkBuilder;
|
|
8
|
+
bold(): ChalkBuilder;
|
|
9
|
+
danger(): ChalkBuilder;
|
|
10
|
+
info(): ChalkBuilder;
|
|
11
|
+
line(): void;
|
|
12
|
+
show(): string;
|
|
13
|
+
private get _value();
|
|
14
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import os from "os";
|
|
3
|
+
export default class ChalkBuilder {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.value = "";
|
|
6
|
+
this.isNewLine = true;
|
|
7
|
+
}
|
|
8
|
+
setValue(value) {
|
|
9
|
+
this.value = value;
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
inline() {
|
|
13
|
+
this.isNewLine = false;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
bold() {
|
|
17
|
+
if (this.clk)
|
|
18
|
+
this.clk = this.clk.bold;
|
|
19
|
+
else
|
|
20
|
+
this.clk = chalk.bold;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
danger() {
|
|
24
|
+
if (this.clk)
|
|
25
|
+
this.clk = this.clk.red;
|
|
26
|
+
else
|
|
27
|
+
this.clk = chalk.red;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
info() {
|
|
31
|
+
if (this.clk)
|
|
32
|
+
this.clk = this.clk.hex("#0CCAF0");
|
|
33
|
+
else
|
|
34
|
+
this.clk = chalk.hex("#0CCAF0");
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
line() {
|
|
38
|
+
console.log("-".repeat(process.stdout.columns));
|
|
39
|
+
}
|
|
40
|
+
show() {
|
|
41
|
+
return this.clk(this._value);
|
|
42
|
+
}
|
|
43
|
+
get _value() {
|
|
44
|
+
if (this.isNewLine)
|
|
45
|
+
return `${this.value}${os.EOL}`;
|
|
46
|
+
return this.value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default class ResponseBuilder {
|
|
2
|
+
protected data?: any;
|
|
3
|
+
protected message: string;
|
|
4
|
+
protected status: number;
|
|
5
|
+
constructor();
|
|
6
|
+
setData(data?: any): ResponseBuilder;
|
|
7
|
+
setMessage(message: string): ResponseBuilder;
|
|
8
|
+
setStatus(status: number): ResponseBuilder;
|
|
9
|
+
send(): globalThis.Response;
|
|
10
|
+
stream(options?: ResponseInit): globalThis.Response;
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cors } from "../utils/utils";
|
|
2
|
+
export default class ResponseBuilder {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.data = null;
|
|
5
|
+
this.message = "Success";
|
|
6
|
+
this.status = 200;
|
|
7
|
+
}
|
|
8
|
+
setData(data) {
|
|
9
|
+
this.data = data;
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
setMessage(message) {
|
|
13
|
+
this.message = message;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
setStatus(status) {
|
|
17
|
+
this.status = status;
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
send() {
|
|
21
|
+
return globalThis.Response.json({
|
|
22
|
+
data: this.data,
|
|
23
|
+
message: this.message,
|
|
24
|
+
status: this.status
|
|
25
|
+
}, {
|
|
26
|
+
headers: {
|
|
27
|
+
...cors()
|
|
28
|
+
},
|
|
29
|
+
status: this.status
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
stream(options = {}) {
|
|
33
|
+
return new globalThis.Response(Bun.file(this.data), {
|
|
34
|
+
...options,
|
|
35
|
+
headers: {
|
|
36
|
+
...cors()
|
|
37
|
+
},
|
|
38
|
+
status: this.status
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IMiddleware } from "@/types/middleware";
|
|
2
|
+
import type { HandlerType, ResourceAction, RouterGroup } from "@/types/router";
|
|
3
|
+
import HttpMethodEnum from "../enums/HttpMethodEnum";
|
|
4
|
+
export interface ResourceOptions {
|
|
5
|
+
only?: Array<ResourceAction>;
|
|
6
|
+
except?: Array<ResourceAction>;
|
|
7
|
+
}
|
|
8
|
+
export default class RouterBuilder {
|
|
9
|
+
private basePath;
|
|
10
|
+
private middlewares;
|
|
11
|
+
prefix(basePath: string): RouterBuilder;
|
|
12
|
+
middleware(...middlewares: Array<IMiddleware>): RouterBuilder;
|
|
13
|
+
group(routes: RouterGroup | Array<RouterGroup>): RouterGroup;
|
|
14
|
+
resources(controller: Record<string, HandlerType>, options?: ResourceOptions): RouterGroup;
|
|
15
|
+
buildSingle(method: HttpMethodEnum, path: string, handler: string | HandlerType): RouterGroup;
|
|
16
|
+
private joinPaths;
|
|
17
|
+
private resolveControllerString;
|
|
18
|
+
private resolveIncludedActions;
|
|
19
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import RouterInvalidException from "../exceptions/RouterInvalidException";
|
|
2
|
+
import { isEmpty } from "../utils/utils";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export default class RouterBuilder {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.basePath = "";
|
|
7
|
+
this.middlewares = [];
|
|
8
|
+
}
|
|
9
|
+
prefix(basePath) {
|
|
10
|
+
this.basePath = basePath;
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
middleware(...middlewares) {
|
|
14
|
+
this.middlewares.push(...middlewares);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
group(routes) {
|
|
18
|
+
const routeList = Array.isArray(routes) ? routes : [routes];
|
|
19
|
+
const newRoutes = {};
|
|
20
|
+
for (const route of routeList) {
|
|
21
|
+
for (const path in route) {
|
|
22
|
+
const fullPath = this.joinPaths(this.basePath, path);
|
|
23
|
+
const routeHandlers = route[path];
|
|
24
|
+
const wrappedHandlers = {};
|
|
25
|
+
for (const method in routeHandlers) {
|
|
26
|
+
let handler = routeHandlers[method];
|
|
27
|
+
for (const middleware of this.middlewares) {
|
|
28
|
+
handler = middleware.handle(handler);
|
|
29
|
+
}
|
|
30
|
+
wrappedHandlers[method] = handler;
|
|
31
|
+
}
|
|
32
|
+
if (isEmpty(newRoutes[fullPath]))
|
|
33
|
+
newRoutes[fullPath] = {};
|
|
34
|
+
Object.assign(newRoutes[fullPath], wrappedHandlers);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return newRoutes;
|
|
38
|
+
}
|
|
39
|
+
resources(controller, options) {
|
|
40
|
+
const allRoutes = {
|
|
41
|
+
"": {
|
|
42
|
+
GET: "index",
|
|
43
|
+
POST: "store"
|
|
44
|
+
},
|
|
45
|
+
":id": {
|
|
46
|
+
GET: "show",
|
|
47
|
+
PUT: "update",
|
|
48
|
+
DELETE: "destroy"
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const includedActions = this.resolveIncludedActions(options);
|
|
52
|
+
const filteredRoutes = {};
|
|
53
|
+
for (const path in allRoutes) {
|
|
54
|
+
const methods = allRoutes[path];
|
|
55
|
+
const methodHandlers = {};
|
|
56
|
+
for (const method in methods) {
|
|
57
|
+
const action = methods[method];
|
|
58
|
+
if (includedActions.has(action) && controller[action]) {
|
|
59
|
+
methodHandlers[method] = controller[action];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(methodHandlers).length > 0) {
|
|
63
|
+
filteredRoutes[path] = methodHandlers;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return this.group(filteredRoutes);
|
|
67
|
+
}
|
|
68
|
+
buildSingle(method, path, handler) {
|
|
69
|
+
const cleanPath = this.joinPaths(this.basePath, path);
|
|
70
|
+
let resolvedHandler = typeof handler === "string" ?
|
|
71
|
+
this.resolveControllerString(handler) :
|
|
72
|
+
handler;
|
|
73
|
+
for (const middleware of this.middlewares) {
|
|
74
|
+
resolvedHandler = middleware.handle(resolvedHandler);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
[cleanPath]: {
|
|
78
|
+
[method]: resolvedHandler
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
joinPaths(base, path) {
|
|
83
|
+
base = base.replace(/\/+$/, "");
|
|
84
|
+
path = path.replace(/^\/+/, "");
|
|
85
|
+
return "/" + [base, path].filter(Boolean).join("/");
|
|
86
|
+
}
|
|
87
|
+
resolveControllerString(definition) {
|
|
88
|
+
const [controllerName, methodName] = definition.split("@");
|
|
89
|
+
if (isEmpty(controllerName) || isEmpty(methodName)) {
|
|
90
|
+
throw new RouterInvalidException(`[RouterInvalidException]: Invalid router controller definition: ${definition}.`);
|
|
91
|
+
}
|
|
92
|
+
const controllerPath = path.resolve(process.cwd(), "app/controllers");
|
|
93
|
+
const location = `${controllerPath}/${controllerName}`;
|
|
94
|
+
let ControllerClass;
|
|
95
|
+
try {
|
|
96
|
+
ControllerClass = require(location).default;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return async (...args) => {
|
|
100
|
+
const module = await import(location);
|
|
101
|
+
const ESMController = module.default;
|
|
102
|
+
const instance = new ESMController();
|
|
103
|
+
if (typeof instance[methodName] !== "function") {
|
|
104
|
+
throw new RouterInvalidException(`[RouterInvalidException]: Method "${methodName}" not found in ${controllerName}.`);
|
|
105
|
+
}
|
|
106
|
+
return instance[methodName](...args);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (isEmpty(ControllerClass)) {
|
|
110
|
+
throw new RouterInvalidException(`[RouterInvalidException]: Controller not found: ${controllerName}.`);
|
|
111
|
+
}
|
|
112
|
+
const instance = new ControllerClass();
|
|
113
|
+
if (typeof instance[methodName] !== "function") {
|
|
114
|
+
throw new RouterInvalidException(`[RouterInvalidException]: Method "${methodName}" not found in ${controllerName}.`);
|
|
115
|
+
}
|
|
116
|
+
return instance[methodName].bind(instance);
|
|
117
|
+
}
|
|
118
|
+
resolveIncludedActions(options) {
|
|
119
|
+
const all = ["index", "store", "show", "update", "destroy"];
|
|
120
|
+
if (options?.only) {
|
|
121
|
+
return new Set(options.only);
|
|
122
|
+
}
|
|
123
|
+
if (options?.except) {
|
|
124
|
+
return new Set(all.filter(action => !options.except.includes(action)));
|
|
125
|
+
}
|
|
126
|
+
return new Set(all);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
client: "pg",
|
|
3
|
+
connection: {
|
|
4
|
+
host: "127.0.0.1",
|
|
5
|
+
port: 5432,
|
|
6
|
+
user: "postgres",
|
|
7
|
+
password: "",
|
|
8
|
+
database: "bejibun"
|
|
9
|
+
},
|
|
10
|
+
migrations: {
|
|
11
|
+
extension: "ts",
|
|
12
|
+
directory: "./database/migrations",
|
|
13
|
+
tableName: "migrations"
|
|
14
|
+
},
|
|
15
|
+
pool: {
|
|
16
|
+
min: 0,
|
|
17
|
+
max: 1
|
|
18
|
+
},
|
|
19
|
+
seeds: {
|
|
20
|
+
extension: "ts",
|
|
21
|
+
directory: "./database/seeders"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export default config;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
var CorsHeaderEnum;
|
|
2
|
+
(function (CorsHeaderEnum) {
|
|
3
|
+
CorsHeaderEnum["Accept"] = "Accept";
|
|
4
|
+
CorsHeaderEnum["Authorization"] = "Authorization";
|
|
5
|
+
CorsHeaderEnum["ContentType"] = "Content-Type";
|
|
6
|
+
CorsHeaderEnum["Origin"] = "Origin";
|
|
7
|
+
CorsHeaderEnum["XRequestedWith"] = "X-Requested-With";
|
|
8
|
+
})(CorsHeaderEnum || (CorsHeaderEnum = {}));
|
|
9
|
+
export default CorsHeaderEnum;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
var HttpMethodEnum;
|
|
2
|
+
(function (HttpMethodEnum) {
|
|
3
|
+
HttpMethodEnum["Connect"] = "CONNECT";
|
|
4
|
+
HttpMethodEnum["Delete"] = "DELETE";
|
|
5
|
+
HttpMethodEnum["Get"] = "GET";
|
|
6
|
+
HttpMethodEnum["Head"] = "HEAD";
|
|
7
|
+
HttpMethodEnum["Options"] = "OPTIONS";
|
|
8
|
+
HttpMethodEnum["Patch"] = "PATCH";
|
|
9
|
+
HttpMethodEnum["Post"] = "POST";
|
|
10
|
+
HttpMethodEnum["Put"] = "PUT";
|
|
11
|
+
HttpMethodEnum["Trace"] = "TRACE";
|
|
12
|
+
})(HttpMethodEnum || (HttpMethodEnum = {}));
|
|
13
|
+
export default HttpMethodEnum;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineValue } from "../utils/utils";
|
|
2
|
+
export default class ModelNotFoundException extends Error {
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ModelNotFoundException";
|
|
6
|
+
this.code = defineValue(code, 404);
|
|
7
|
+
if (Error.captureStackTrace) {
|
|
8
|
+
Error.captureStackTrace(this, ModelNotFoundException);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineValue } from "../utils/utils";
|
|
2
|
+
export default class RouterInvalidException extends Error {
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "RouterInvalidException";
|
|
6
|
+
this.code = defineValue(code, 500);
|
|
7
|
+
if (Error.captureStackTrace) {
|
|
8
|
+
Error.captureStackTrace(this, RouterInvalidException);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineValue } from "../utils/utils";
|
|
2
|
+
export default class ValidatorException extends Error {
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ValidatorException";
|
|
6
|
+
this.code = defineValue(code, 422);
|
|
7
|
+
if (Error.captureStackTrace) {
|
|
8
|
+
Error.captureStackTrace(this, ValidatorException);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/facades/Enum.d.ts
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ResponseBuilder from "../builders/ResponseBuilder";
|
|
2
|
+
export default class Response {
|
|
3
|
+
static setData(data) {
|
|
4
|
+
return new ResponseBuilder().setData(data);
|
|
5
|
+
}
|
|
6
|
+
static setMessage(message) {
|
|
7
|
+
return new ResponseBuilder().setMessage(message);
|
|
8
|
+
}
|
|
9
|
+
static setStatus(status) {
|
|
10
|
+
return new ResponseBuilder().setStatus(status);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IMiddleware } from "@/types/middleware";
|
|
2
|
+
import type { HandlerType, RouterGroup } from "@/types/router";
|
|
3
|
+
import RouterBuilder, { ResourceOptions } from "../builders/RouterBuilder";
|
|
4
|
+
import HttpMethodEnum from "../enums/HttpMethodEnum";
|
|
5
|
+
export default class Router {
|
|
6
|
+
static prefix(basePath: string): RouterBuilder;
|
|
7
|
+
static middleware(...middlewares: Array<IMiddleware>): RouterBuilder;
|
|
8
|
+
static resources(controller: Record<string, HandlerType>, options?: ResourceOptions): RouterGroup;
|
|
9
|
+
static group(routes: RouterGroup, prefix?: string, middlewares?: Array<IMiddleware>): RouterGroup;
|
|
10
|
+
static connect(path: string, handler: string | HandlerType): RouterGroup;
|
|
11
|
+
static delete(path: string, handler: string | HandlerType): RouterGroup;
|
|
12
|
+
static get(path: string, handler: string | HandlerType): RouterGroup;
|
|
13
|
+
static head(path: string, handler: string | HandlerType): RouterGroup;
|
|
14
|
+
static options(path: string, handler: string | HandlerType): RouterGroup;
|
|
15
|
+
static patch(path: string, handler: string | HandlerType): RouterGroup;
|
|
16
|
+
static post(path: string, handler: string | HandlerType): RouterGroup;
|
|
17
|
+
static put(path: string, handler: string | HandlerType): RouterGroup;
|
|
18
|
+
static trace(path: string, handler: string | HandlerType): RouterGroup;
|
|
19
|
+
static match(methods: Array<HttpMethodEnum>, path: string, handler: string | HandlerType): RouterGroup;
|
|
20
|
+
static any(path: string, handler: string | HandlerType): RouterGroup;
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import RouterBuilder from "../builders/RouterBuilder";
|
|
2
|
+
import HttpMethodEnum from "../enums/HttpMethodEnum";
|
|
3
|
+
import Enum from "../facades/Enum";
|
|
4
|
+
import { isEmpty } from "../utils/utils";
|
|
5
|
+
export default class Router {
|
|
6
|
+
static prefix(basePath) {
|
|
7
|
+
return new RouterBuilder().prefix(basePath);
|
|
8
|
+
}
|
|
9
|
+
static middleware(...middlewares) {
|
|
10
|
+
return new RouterBuilder().middleware(...middlewares);
|
|
11
|
+
}
|
|
12
|
+
static resources(controller, options) {
|
|
13
|
+
return new RouterBuilder().resources(controller, options);
|
|
14
|
+
}
|
|
15
|
+
static group(routes, prefix, middlewares) {
|
|
16
|
+
const builder = new RouterBuilder();
|
|
17
|
+
if (prefix)
|
|
18
|
+
builder.prefix(prefix);
|
|
19
|
+
if (middlewares?.length)
|
|
20
|
+
builder.middleware(...middlewares);
|
|
21
|
+
return builder.group(routes);
|
|
22
|
+
}
|
|
23
|
+
static connect(path, handler) {
|
|
24
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Connect, path, handler);
|
|
25
|
+
}
|
|
26
|
+
static delete(path, handler) {
|
|
27
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Delete, path, handler);
|
|
28
|
+
}
|
|
29
|
+
static get(path, handler) {
|
|
30
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Get, path, handler);
|
|
31
|
+
}
|
|
32
|
+
static head(path, handler) {
|
|
33
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Head, path, handler);
|
|
34
|
+
}
|
|
35
|
+
static options(path, handler) {
|
|
36
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Options, path, handler);
|
|
37
|
+
}
|
|
38
|
+
static patch(path, handler) {
|
|
39
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Patch, path, handler);
|
|
40
|
+
}
|
|
41
|
+
static post(path, handler) {
|
|
42
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Post, path, handler);
|
|
43
|
+
}
|
|
44
|
+
static put(path, handler) {
|
|
45
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Put, path, handler);
|
|
46
|
+
}
|
|
47
|
+
static trace(path, handler) {
|
|
48
|
+
return new RouterBuilder().buildSingle(HttpMethodEnum.Trace, path, handler);
|
|
49
|
+
}
|
|
50
|
+
static match(methods, path, handler) {
|
|
51
|
+
const builder = new RouterBuilder();
|
|
52
|
+
const routeMap = {};
|
|
53
|
+
for (const method of methods) {
|
|
54
|
+
const single = builder.buildSingle(method, path, handler);
|
|
55
|
+
const fullPath = Object.keys(single)[0];
|
|
56
|
+
const handlers = single[fullPath];
|
|
57
|
+
if (isEmpty(routeMap[fullPath]))
|
|
58
|
+
routeMap[fullPath] = {};
|
|
59
|
+
Object.assign(routeMap[fullPath], handlers);
|
|
60
|
+
}
|
|
61
|
+
return routeMap;
|
|
62
|
+
}
|
|
63
|
+
static any(path, handler) {
|
|
64
|
+
return this.match(Enum.setEnums(HttpMethodEnum).toArray(), path, handler);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { QueryBuilder, ModelClass, Model } from "objection";
|
|
2
|
+
export default class SoftDeletes<M extends Model, R = M[]> extends QueryBuilder<M, R> {
|
|
3
|
+
private hasFilterApplied;
|
|
4
|
+
constructor(modelClass: ModelClass<M>);
|
|
5
|
+
withTrashed(): this;
|
|
6
|
+
onlyTrashed(): this;
|
|
7
|
+
delete(): QueryBuilder<M, number>;
|
|
8
|
+
del(): QueryBuilder<M, number>;
|
|
9
|
+
forceDelete(): QueryBuilder<M, number>;
|
|
10
|
+
restore(): QueryBuilder<M, number>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { QueryBuilder } from "objection";
|
|
3
|
+
export default class SoftDeletes extends QueryBuilder {
|
|
4
|
+
constructor(modelClass) {
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
super(modelClass);
|
|
7
|
+
this.hasFilterApplied = false;
|
|
8
|
+
this.onBuild((builder) => {
|
|
9
|
+
const context = this.context();
|
|
10
|
+
if (!this.hasFilterApplied) {
|
|
11
|
+
const tableName = this.modelClass().tableName;
|
|
12
|
+
if (context.onlyTrashed) {
|
|
13
|
+
builder.whereNotNull(`${tableName}.${this.modelClass().deletedColumn}`);
|
|
14
|
+
}
|
|
15
|
+
else if (!context.withTrashed) {
|
|
16
|
+
builder.whereNull(`${tableName}.${this.modelClass().deletedColumn}`);
|
|
17
|
+
}
|
|
18
|
+
this.hasFilterApplied = true;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
withTrashed() {
|
|
23
|
+
return this.context({
|
|
24
|
+
...this.context(),
|
|
25
|
+
withTrashed: true
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
onlyTrashed() {
|
|
29
|
+
return this.context({
|
|
30
|
+
...this.context(),
|
|
31
|
+
onlyTrashed: true
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
delete() {
|
|
35
|
+
return this.update({
|
|
36
|
+
[this.modelClass().deletedColumn]: DateTime.now()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
del() {
|
|
40
|
+
return this.delete();
|
|
41
|
+
}
|
|
42
|
+
forceDelete() {
|
|
43
|
+
return super.delete();
|
|
44
|
+
}
|
|
45
|
+
restore() {
|
|
46
|
+
return this.onlyTrashed().update({
|
|
47
|
+
[this.modelClass().deletedColumn]: null
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1 @@
|
|
|
1
|
-
import Enum from "./facades/Enum";
|
|
2
|
-
import Str from "./facades/Str";
|
|
3
|
-
export { Enum, Str };
|
|
4
1
|
export * from "./utils/utils";
|
|
5
|
-
declare const _default: {
|
|
6
|
-
isEmpty: (value: any) => boolean;
|
|
7
|
-
isNotEmpty: (value: any) => boolean;
|
|
8
|
-
defineValue: (value: any, defaultValue?: any) => any;
|
|
9
|
-
ask: (question: string) => Promise<string>;
|
|
10
|
-
Enum: typeof Enum;
|
|
11
|
-
Str: typeof Str;
|
|
12
|
-
};
|
|
13
|
-
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare module "@vinejs/vine" {
|
|
2
|
+
interface VineNumber {
|
|
3
|
+
exists(tableOrOptions: string | { table: string; column?: string }, column?: string): this;
|
|
4
|
+
unique(tableOrOptions: string | { table: string; column?: string }, column?: string): this;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface VineString {
|
|
8
|
+
exists(tableOrOptions: string | { table: string; column?: string }, column?: string): this;
|
|
9
|
+
unique(tableOrOptions: string | { table: string; column?: string }, column?: string): this;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {};
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export declare const isEmpty: (value: any) => boolean;
|
|
|
2
2
|
export declare const isNotEmpty: (value: any) => boolean;
|
|
3
3
|
export declare const defineValue: (value: any, defaultValue?: any) => any;
|
|
4
4
|
export declare const ask: (question: string) => Promise<string>;
|
|
5
|
+
export declare const cors: () => Record<string, string>;
|
package/dist/utils/utils.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
1
3
|
import readline from "readline";
|
|
4
|
+
import CorsHeaderEnum from "../enums/CorsHeaderEnum";
|
|
5
|
+
import HttpMethodEnum from "../enums/HttpMethodEnum";
|
|
6
|
+
import Enum from "../facades/Enum";
|
|
2
7
|
export const isEmpty = (value) => {
|
|
3
8
|
return (value === undefined ||
|
|
4
9
|
value === null ||
|
|
@@ -22,10 +27,34 @@ export const ask = (question) => {
|
|
|
22
27
|
input: process.stdin,
|
|
23
28
|
output: process.stdout
|
|
24
29
|
});
|
|
25
|
-
return new Promise(
|
|
30
|
+
return new Promise(resolve => {
|
|
26
31
|
return rl.question(question, (answer) => {
|
|
27
32
|
rl.close();
|
|
28
33
|
resolve(answer.trim());
|
|
29
34
|
});
|
|
30
35
|
});
|
|
31
36
|
};
|
|
37
|
+
export const cors = () => {
|
|
38
|
+
const configPath = path.resolve(process.cwd(), "config/cors.ts");
|
|
39
|
+
let corsConfig;
|
|
40
|
+
if (fs.existsSync(configPath))
|
|
41
|
+
corsConfig = require(configPath).default;
|
|
42
|
+
else
|
|
43
|
+
corsConfig = require("../config/cors").default;
|
|
44
|
+
const headers = {
|
|
45
|
+
"Access-Control-Allow-Origin": corsConfig.origin,
|
|
46
|
+
"Access-Control-Allow-Headers": Array.isArray(corsConfig.allowedHeaders)
|
|
47
|
+
? corsConfig.allowedHeaders.join(", ")
|
|
48
|
+
: Enum.setEnums(CorsHeaderEnum).toArray().map((value) => value.value).join(", "),
|
|
49
|
+
"Access-Control-Allow-Methods": Array.isArray(corsConfig.methods)
|
|
50
|
+
? corsConfig.methods.join(", ")
|
|
51
|
+
: Enum.setEnums(HttpMethodEnum).toArray().map((value) => value.value).join(", ")
|
|
52
|
+
};
|
|
53
|
+
if (corsConfig.exposedHeaders.length > 0)
|
|
54
|
+
headers["Access-Control-Expose-Headers"] = corsConfig.exposedHeaders.join(", ");
|
|
55
|
+
if (corsConfig.credentials)
|
|
56
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
57
|
+
if (corsConfig.maxAge)
|
|
58
|
+
headers["Access-Control-Max-Age"] = corsConfig.maxAge.toString();
|
|
59
|
+
return headers;
|
|
60
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import vine, { VineNumber, VineString } from "@vinejs/vine";
|
|
2
|
+
import BaseModel from "../../bases/BaseModel";
|
|
3
|
+
import { defineValue, isEmpty } from "../../utils/utils";
|
|
4
|
+
const exists = async (value, options, field) => {
|
|
5
|
+
if (!field.isValid)
|
|
6
|
+
return;
|
|
7
|
+
const column = defineValue(options.column, field.name);
|
|
8
|
+
let query = options.table;
|
|
9
|
+
if (options.withTrashed)
|
|
10
|
+
query = query.withTrashed();
|
|
11
|
+
else
|
|
12
|
+
query = query.query();
|
|
13
|
+
const row = await query.where(column, value).first();
|
|
14
|
+
if (isEmpty(row))
|
|
15
|
+
field.report("The {{ field }} field doesn't exists", "exists", field);
|
|
16
|
+
};
|
|
17
|
+
const existsRule = vine.createRule(exists, { isAsync: true });
|
|
18
|
+
const registerExistsMacro = (Type) => {
|
|
19
|
+
Type.macro("exists", function (tableOrOptions, column, withTrashed) {
|
|
20
|
+
const isModel = typeof tableOrOptions === "function" && Object.prototype.isPrototypeOf.call(BaseModel, tableOrOptions);
|
|
21
|
+
const options = isModel
|
|
22
|
+
? { table: tableOrOptions, column, withTrashed }
|
|
23
|
+
: tableOrOptions;
|
|
24
|
+
return this.use(existsRule(options));
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
registerExistsMacro(VineString);
|
|
28
|
+
registerExistsMacro(VineNumber);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import vine, { VineNumber, VineString } from "@vinejs/vine";
|
|
2
|
+
import BaseModel from "../../bases/BaseModel";
|
|
3
|
+
import { defineValue, isNotEmpty } from "../../utils/utils";
|
|
4
|
+
const unique = async (value, options, field) => {
|
|
5
|
+
if (!field.isValid)
|
|
6
|
+
return;
|
|
7
|
+
const column = defineValue(options.column, field.name);
|
|
8
|
+
let query = options.table;
|
|
9
|
+
if (options.withTrashed)
|
|
10
|
+
query = query.withTrashed();
|
|
11
|
+
else
|
|
12
|
+
query = query.query();
|
|
13
|
+
const row = await query.where(column, value).first();
|
|
14
|
+
if (isNotEmpty(row))
|
|
15
|
+
field.report("The {{ field }} field is already exists", "unique", field);
|
|
16
|
+
};
|
|
17
|
+
const uniqueRule = vine.createRule(unique, { isAsync: true });
|
|
18
|
+
const registerUniqueMacro = (Type) => {
|
|
19
|
+
Type.macro("unique", function (tableOrOptions, column, withTrashed) {
|
|
20
|
+
const isModel = typeof tableOrOptions === "function" && Object.prototype.isPrototypeOf.call(BaseModel, tableOrOptions);
|
|
21
|
+
const options = isModel
|
|
22
|
+
? { table: tableOrOptions, column, withTrashed }
|
|
23
|
+
: tableOrOptions;
|
|
24
|
+
return this.use(uniqueRule(options));
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
registerUniqueMacro(VineString);
|
|
28
|
+
registerUniqueMacro(VineNumber);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bejibun/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Core of Bejibun Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -22,19 +22,35 @@
|
|
|
22
22
|
"type": "module",
|
|
23
23
|
"main": "./dist/index.js",
|
|
24
24
|
"module": "./dist/index.js",
|
|
25
|
-
"types": "./
|
|
25
|
+
"types": "./types/index.d.ts",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
28
|
"import": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts"
|
|
30
30
|
},
|
|
31
|
-
"./
|
|
32
|
-
"import": "./dist/
|
|
33
|
-
"types": "./dist/
|
|
31
|
+
"./bootstrap": {
|
|
32
|
+
"import": "./dist/bootstrap.js",
|
|
33
|
+
"types": "./dist/bootstrap.d.ts"
|
|
34
34
|
},
|
|
35
|
-
"./
|
|
36
|
-
"import": "./dist/
|
|
37
|
-
"types": "./dist/
|
|
35
|
+
"./bases/*": {
|
|
36
|
+
"import": "./dist/bases/*.js",
|
|
37
|
+
"types": "./dist/bases/*.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"./enums/*": {
|
|
40
|
+
"import": "./dist/enums/*.js",
|
|
41
|
+
"types": "./dist/enums/*.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"./exceptions/*": {
|
|
44
|
+
"import": "./dist/exceptions/*.js",
|
|
45
|
+
"types": "./dist/exceptions/*.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./facades/*": {
|
|
48
|
+
"import": "./dist/facades/*.js",
|
|
49
|
+
"types": "./dist/facades/*.d.ts"
|
|
50
|
+
},
|
|
51
|
+
"./types/*": {
|
|
52
|
+
"import": "./dist/types/*.js",
|
|
53
|
+
"types": "./dist/types/*.d.ts"
|
|
38
54
|
}
|
|
39
55
|
},
|
|
40
56
|
"files": [
|
|
@@ -42,11 +58,20 @@
|
|
|
42
58
|
],
|
|
43
59
|
"scripts": {
|
|
44
60
|
"clean": "rm -rf dist",
|
|
45
|
-
"build": "bun run clean && tsc -p tsconfig.json && tsc-alias -p tsconfig.json"
|
|
61
|
+
"build": "bun run clean && tsc -p tsconfig.json && tsc-alias -p tsconfig.json && cp -rf src/types dist/types",
|
|
62
|
+
"publish": "bun run build && npm publish --access public"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@vinejs/vine": "^3.0.1",
|
|
66
|
+
"chalk": "^5.6.2",
|
|
67
|
+
"knex": "^3.1.0",
|
|
68
|
+
"luxon": "^3.7.2",
|
|
69
|
+
"objection": "^3.1.5",
|
|
70
|
+
"pg": "^8.16.3"
|
|
46
71
|
},
|
|
47
|
-
"dependencies": {},
|
|
48
72
|
"devDependencies": {
|
|
49
73
|
"@types/bun": "latest",
|
|
74
|
+
"@types/luxon": "^3.7.1",
|
|
50
75
|
"tsc-alias": "^1.8.16",
|
|
51
76
|
"typescript": "^5.9.3"
|
|
52
77
|
},
|