@kava/kava-api-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/bun.lock +160 -0
  2. package/dist/auth.util.d.ts +23 -0
  3. package/dist/auth.util.js +175 -0
  4. package/dist/auth.util.js.map +1 -0
  5. package/dist/context.util.d.ts +7 -0
  6. package/dist/context.util.js +11 -0
  7. package/dist/context.util.js.map +1 -0
  8. package/dist/controller.util.d.ts +118 -0
  9. package/dist/controller.util.js +144 -0
  10. package/dist/controller.util.js.map +1 -0
  11. package/dist/conversion.util.d.ts +8 -0
  12. package/dist/conversion.util.js +52 -0
  13. package/dist/conversion.util.js.map +1 -0
  14. package/dist/db.util.d.ts +80 -0
  15. package/dist/db.util.js +166 -0
  16. package/dist/db.util.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.js +14 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/logger.util.d.ts +30 -0
  21. package/dist/logger.util.js +117 -0
  22. package/dist/logger.util.js.map +1 -0
  23. package/dist/mail.util.d.ts +21 -0
  24. package/dist/mail.util.js +53 -0
  25. package/dist/mail.util.js.map +1 -0
  26. package/dist/middleware.util.d.ts +263 -0
  27. package/dist/middleware.util.js +233 -0
  28. package/dist/middleware.util.js.map +1 -0
  29. package/dist/model.util.d.ts +204 -0
  30. package/dist/model.util.js +1495 -0
  31. package/dist/model.util.js.map +1 -0
  32. package/dist/permission.util.d.ts +38 -0
  33. package/dist/permission.util.js +91 -0
  34. package/dist/permission.util.js.map +1 -0
  35. package/dist/route.util.d.ts +1 -0
  36. package/dist/route.util.js +12 -0
  37. package/dist/route.util.js.map +1 -0
  38. package/dist/storage.util.d.ts +56 -0
  39. package/dist/storage.util.js +82 -0
  40. package/dist/storage.util.js.map +1 -0
  41. package/dist/validation.util.d.ts +7 -0
  42. package/dist/validation.util.js +237 -0
  43. package/dist/validation.util.js.map +1 -0
  44. package/package.json +34 -0
  45. package/src/auth.util.ts +242 -0
  46. package/src/context.util.ts +17 -0
  47. package/src/controller.util.ts +237 -0
  48. package/src/conversion.util.ts +65 -0
  49. package/src/db.util.ts +405 -0
  50. package/src/index.ts +13 -0
  51. package/src/logger.util.ts +170 -0
  52. package/src/mail.util.ts +86 -0
  53. package/src/middleware.util.ts +289 -0
  54. package/src/model.util.ts +2211 -0
  55. package/src/permission.util.ts +136 -0
  56. package/src/route.util.ts +12 -0
  57. package/src/storage.util.ts +102 -0
  58. package/src/validation.util.ts +338 -0
  59. package/tsconfig.json +23 -0
@@ -0,0 +1,237 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import "elysia";
4
+ import { Elysia, Context } from "elysia";
5
+ import { validate, logger, KeyPermission, db, ValidationRules, ValidationRule } from "@utils";
6
+
7
+
8
+
9
+ declare module "elysia" {
10
+ interface ControllerContext extends Context {
11
+ getQuery : {
12
+ paginate : number;
13
+ page : number;
14
+ sort : string[];
15
+ filter : Record<string, string>;
16
+ search : string;
17
+ searchable : string[];
18
+ selectable : string[];
19
+ selectableOption : string[];
20
+ expand : string[];
21
+ };
22
+
23
+ responseData : (
24
+ data : any[],
25
+ totalRow ?: number,
26
+ message ?: string,
27
+ columns ?: string[],
28
+ access ?: string[]
29
+ ) => {
30
+ status : number;
31
+ body : any
32
+ };
33
+
34
+ validation : <T extends object>(rules: Partial<Record<keyof T | string, ValidationRule[] | string>>) => any;
35
+ responseError : (error: any, section?: string, message?: string, debug?: boolean) => any;
36
+ responseErrorValidation : (errors: Record<string, string[]>) => any;
37
+ responseSaved : (data: any, message?: string) => any;
38
+ responseSuccess : (data: any, message?: string) => any;
39
+ responseForbidden : (message?: string) => any;
40
+ uploadFile : (file: File, folder?: string) => Promise<string>;
41
+ deleteFile : (filePath: string) => void;
42
+ user ?: any
43
+ permissions ?: KeyPermission[],
44
+ payload : Record<string, any>
45
+ }
46
+ }
47
+
48
+ export type ValidationRulesFor<T> = Partial<
49
+ Record<keyof T | string, ValidationRule[] | string>
50
+ >
51
+
52
+
53
+ export const controller = (app: Elysia) => app.derive(({ query, body, status }) => ({
54
+
55
+ // =====================================>
56
+ // ## Basic fetching data query
57
+ // =====================================>
58
+ getQuery: {
59
+ page : query.page ? Number(query.page) : 1,
60
+ paginate : query.paginate ? Number(query.paginate) : 10,
61
+ search : query.search ? query.search : "",
62
+ sort : query.sort ? JSON.parse(query.sort) : ["created_at desc"],
63
+ filter : query.filter ? JSON.parse(query.filter) : [],
64
+ searchable : query.searchable ? JSON.parse(query.searchable) : [],
65
+ selectable : query.selectable ? JSON.parse(query.selectable) : [],
66
+ selectableOption : query.selectableOption ? JSON.parse(query.selectableOption) : [],
67
+ expand : query.expand ? JSON.parse(query.expand) : [],
68
+ },
69
+
70
+
71
+
72
+ // ===================================>
73
+ // ## Validation request body
74
+ // ===================================>
75
+ validation: async <T extends object>(
76
+ rules: Partial<Record<keyof T | string, ValidationRules[] | string>>
77
+ ) => {
78
+ const result = await validate(
79
+ body as Record<string, any>,
80
+ rules as ValidationRules
81
+ )
82
+
83
+ if (!result.valid) {
84
+ throw status(422, {
85
+ message: "Error: Unprocessable Entity!",
86
+ errors: result.errors,
87
+ })
88
+ }
89
+ },
90
+
91
+
92
+
93
+
94
+ // ====================================>
95
+ // ## Response error validation
96
+ // ====================================>
97
+ responseErrorValidation: (errors: Record<string, string[]>) => {
98
+ throw status(422, {
99
+ message: "Error: Unprocessable Entity!",
100
+ errors: errors,
101
+ })
102
+ },
103
+
104
+
105
+
106
+ // ====================================>
107
+ // ## Response error
108
+ // ====================================>
109
+ responseError: (error: any, section?: string, message?: string, debug = (process.env.APP_DEBUG || true)) => {
110
+ logger.error(`Error: ${error}`, { error: error, feature: section })
111
+
112
+ if (debug) {
113
+ throw status(500, {
114
+ message : message ?? "Error: Server Side Having Problem!",
115
+ error : error?.message ?? "unknown",
116
+ section : section ?? "unknown",
117
+ })
118
+ }
119
+
120
+ throw status(500, {
121
+ message: message ?? "Error: Server Side Having Problem!"
122
+ })
123
+ },
124
+
125
+
126
+ // ====================================>
127
+ // ## Response Forbidden
128
+ // ====================================>
129
+ responseForbidden: (message?: string) => {
130
+ throw status(403, {
131
+ message: message ?? "Access Forbidden!"
132
+ })
133
+ },
134
+
135
+
136
+ // ====================================>
137
+ // ## Response record
138
+ // ====================================>
139
+ responseData: (data: any[], totalRow?: number, message?: string) => {
140
+ throw status(200, {
141
+ message : message ?? (data.length ? "Success" : "Empty data"),
142
+ data : data ?? [],
143
+ total_row : totalRow ?? null,
144
+ });
145
+ },
146
+
147
+
148
+
149
+ // ===================================>
150
+ // ## Response success
151
+ // ===================================>
152
+ responseSuccess: (data: any, message?: string, code?: 200 | 201) => {
153
+ throw status(code || 200, {
154
+ message : message ?? "Success",
155
+ data : data ?? [],
156
+ })
157
+ },
158
+
159
+
160
+
161
+ // ===================================>
162
+ // ## Response saved record
163
+ // ===================================>
164
+ responseSaved: (data: any, message?: string) => {
165
+ throw status(201, {
166
+ message : message ?? "Success",
167
+ data : data ?? [],
168
+ })
169
+ },
170
+
171
+
172
+
173
+ // ===================================>
174
+ // ## Upload file
175
+ // ===================================>
176
+ uploadFile: async (file: File, folder = "uploads", options?: { disk?: "public" | "private", owner_id?: number, permissions?: { user_id?: number; role_id?: number }[]}): Promise<string> => {
177
+ const disk = options?.disk ?? "public"
178
+
179
+ const dir = path.resolve("storage", disk, folder);
180
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
181
+
182
+ const fileName = `${Date.now().toString(36)}${Math.random().toString(36).substring(2, 18)}${path.extname(file.name).toLowerCase()}`;
183
+ const filePath = path.join(dir, fileName);
184
+
185
+ const buffer = Buffer.from(await file.arrayBuffer());
186
+
187
+ fs.writeFileSync(filePath, buffer);
188
+
189
+ const relativePath = `/${folder}/${fileName}`
190
+
191
+ if(options) {
192
+ const [storage] = await db("storages").insert({
193
+ user_id : options?.owner_id ?? null,
194
+ disk : disk,
195
+ path : relativePath,
196
+ filename : file.name,
197
+ filetype : file.type,
198
+ filesize : buffer.length,
199
+ created_at : new Date(),
200
+ }).returning(["id"])
201
+
202
+ if (options?.permissions?.length) {
203
+ const permissions = options.permissions.map(p => ({
204
+ storage_id : storage.id,
205
+ user_id : p.user_id ?? null,
206
+ role_id : p.role_id ?? null,
207
+ created_at : new Date(),
208
+ }))
209
+
210
+ await db("storage_permissions").insert(permissions)
211
+ }
212
+ }
213
+
214
+
215
+ return relativePath
216
+ },
217
+
218
+
219
+
220
+ // ==================================>
221
+ // ## Delete File
222
+ // ==================================>
223
+ deleteFile: async (filePath: string) => {
224
+ if (fs.existsSync(filePath)) {
225
+ const record = await db("storages").where("path", filePath).first()
226
+
227
+ if(record) {
228
+ await db("storages").where("id", record.id).delete()
229
+ await db("storage_permissions").where("storage_id", record.id).delete()
230
+ }
231
+
232
+ fs.unlinkSync(filePath); return true;
233
+ }
234
+
235
+ return false;
236
+ },
237
+ }));
@@ -0,0 +1,65 @@
1
+ export const conversion = {
2
+
3
+ // =============================>
4
+ // ## Conversion: String formatter
5
+ // =============================>
6
+ strSnake(value: string, delimiter: string = "_"): string {
7
+ return toWords(value).join(delimiter)
8
+ },
9
+
10
+ strSlug(value: string, delimiter: string = "-"): string {
11
+ return toWords(value).join(delimiter);
12
+ },
13
+
14
+ strCamel(value: string, delimiter: string = ""): string {
15
+ return toWords(value).map((w, i) => i === 0 ? w : w[0].toUpperCase() + w.slice(1)).join(delimiter);
16
+ },
17
+
18
+ strPascal(value: string, delimiter: string = ""): string {
19
+ return toWords(value).map(w => w[0].toUpperCase() + w.slice(1)).join(delimiter);
20
+ },
21
+
22
+ strPlural(value: string): string {
23
+ const match = value.match(/^(.*?)([A-Za-z]+)$/)
24
+ if (!match) return value
25
+
26
+ const [, prefix, word] = match
27
+
28
+ if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) {
29
+ return prefix + word.slice(0, -1) + "ies"
30
+ }
31
+
32
+ if (!word.endsWith("s")) return prefix + word + "s"
33
+
34
+ return value
35
+ },
36
+
37
+ strSingular(value: string): string {
38
+ const match = value.match(/^(.*?)([A-Za-z]+)$/)
39
+ if (!match) return value
40
+
41
+ const [, prefix, word] = match
42
+
43
+ if (word.endsWith("ies")) {
44
+ return prefix + word.slice(0, -3) + "y"
45
+ }
46
+
47
+ if (word.endsWith("s") && !word.endsWith("ss")) {
48
+ return prefix + word.slice(0, -1)
49
+ }
50
+
51
+ return value
52
+ }
53
+ };
54
+
55
+
56
+
57
+ function toWords(value: string): string[] {
58
+ return value
59
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
60
+ .replace(/[_\-\s]+/g, " ")
61
+ .trim()
62
+ .toLowerCase()
63
+ .split(" ")
64
+ .filter(Boolean)
65
+ }
package/src/db.util.ts ADDED
@@ -0,0 +1,405 @@
1
+ import knex, { Knex } from 'knex'
2
+
3
+ // ==============================
4
+ // ## Driver resolver
5
+ // ==============================
6
+ function resolveDriver(conn?: string): 'pg' | 'mysql2' {
7
+ if (!conn) return 'pg'
8
+
9
+ const v = conn.toLowerCase()
10
+ if (['pg', 'pgsql', 'postgres'].includes(v)) return 'pg'
11
+ if (['mysql', 'mysql2'].includes(v)) return 'mysql2'
12
+
13
+ throw new Error(`Unsupported DB_CONNECTION: ${conn}`)
14
+ }
15
+
16
+ // ==============================
17
+ // ## Knex factory
18
+ // ==============================
19
+ function createKnex(config: {
20
+ client ?: string
21
+ host ?: string
22
+ port ?: number
23
+ user ?: string
24
+ password ?: string
25
+ database ?: string
26
+ }) : Knex {
27
+ const client = resolveDriver(config.client)
28
+
29
+ return knex({
30
+ client,
31
+ connection: {
32
+ host : config.host ?? '127.0.0.1',
33
+ port : config.port ?? (client === 'mysql2' ? 3306 : 5432),
34
+ user : config.user ?? 'postgres',
35
+ password : config.password ?? 'password',
36
+ database : config.database ?? 'db_elysia_light',
37
+ },
38
+ pool: { min: 2, max: 10 },
39
+ })
40
+ }
41
+
42
+ // ==============================
43
+ // ## Connection registry
44
+ // ==============================
45
+ const connections: Record<string, Knex> = {}
46
+
47
+ // ==============================
48
+ // ## Default connection
49
+ // ==============================
50
+ const DEFAULT_NAME = 'default'
51
+
52
+ connections[DEFAULT_NAME] = createKnex({
53
+ client : process.env.DB_CONNECTION,
54
+ host : process.env.DB_HOST,
55
+ port : Number(process.env.DB_PORT),
56
+ user : process.env.DB_USERNAME,
57
+ password : process.env.DB_PASSWORD,
58
+ database : process.env.DB_DATABASE,
59
+ })
60
+
61
+ // ==============================
62
+ // ## Export default db
63
+ // ==============================
64
+ export const db: Knex = connections[DEFAULT_NAME]
65
+
66
+ // ==============================
67
+ // ## Named connection (multiple DB)
68
+ // ==============================
69
+ export function useDB(
70
+ name : string,
71
+ config ?: {
72
+ client ?: string
73
+ host ?: string
74
+ port ?: number
75
+ user ?: string
76
+ password ?: string
77
+ database ?: string
78
+ }
79
+ ): Knex {
80
+ if (!connections[name]) {
81
+ if (!config) {
82
+ throw new Error(`DB connection "${name}" not found`)
83
+ }
84
+ connections[name] = createKnex(config)
85
+ }
86
+
87
+ return connections[name]
88
+ }
89
+
90
+ // ==============================
91
+ // ## Close all connections (CLI safe)
92
+ // ==============================
93
+ export async function closeAllDB() {
94
+ await Promise.all( Object.values(connections).map(db => db.destroy()))
95
+ }
96
+
97
+
98
+
99
+
100
+
101
+ declare module "knex" {
102
+ namespace Knex {
103
+ interface QueryBuilder<TRecord = any, TResult = any> {
104
+ where(...args: any[]): this
105
+ orWhere(...args: any[]): this
106
+ whereNot(...args: any[]): this
107
+
108
+ whereNull(...args: any[]): this
109
+ whereNotNull(...args: any[]): this
110
+
111
+ whereIn(...args: any[]): this
112
+ whereNotIn(...args: any[]): this
113
+
114
+ whereBetween(...args: any[]): this
115
+ whereNotBetween(...args: any[]): this
116
+
117
+ whereExists(...args: any[]): this
118
+ whereNotExists(...args: any[]): this
119
+
120
+ // ===== JOIN =====
121
+ join(...args: any[]): this
122
+ leftJoin(...args: any[]): this
123
+ rightJoin(...args: any[]): this
124
+ innerJoin(...args: any[]): this
125
+
126
+ // ===== SELECT =====
127
+ select(...args: any[]): this
128
+ distinct(...args: any[]): this
129
+
130
+ // ===== ORDER / LIMIT =====
131
+ orderBy(...args: any[]): this
132
+ orderByRaw(...args: any[]): this
133
+
134
+ limit(...args: any[]): this
135
+ offset(...args: any[]): this
136
+
137
+ // ===== GROUP =====
138
+ groupBy(...args: any[]): this
139
+ having(...args: any[]): this
140
+
141
+ // ===== RAW =====
142
+ whereRaw(...args: any[]): this
143
+ selectRaw(...args: any[]): this
144
+
145
+ joinWith(
146
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
147
+ table: string,
148
+ relation:
149
+ | {
150
+ localKey: string
151
+ foreignKey: string
152
+ }
153
+ | {
154
+ pivotTable: string
155
+ localKey: string
156
+ pivotLocalKey: string
157
+ pivotForeignKey: string
158
+ foreignKey: string
159
+ },
160
+ as: string,
161
+ callback?: (qb: Knex.QueryBuilder) => void
162
+ ): QueryBuilder<TRecord, TResult>
163
+
164
+ whereJoinHas(
165
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
166
+ table: string,
167
+ relation:
168
+ | string
169
+ | {
170
+ pivotTable: string
171
+ localKey: string
172
+ pivotLocalKey: string
173
+ pivotForeignKey: string
174
+ foreignKey: string
175
+ },
176
+ foreignKey?: string,
177
+ callback?: (qb: Knex.QueryBuilder) => void
178
+ ): QueryBuilder<TRecord, TResult>
179
+
180
+ orWhereJoinHas(
181
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
182
+ table: string,
183
+ relation:
184
+ | string
185
+ | {
186
+ pivotTable: string
187
+ localKey: string
188
+ pivotLocalKey: string
189
+ pivotForeignKey: string
190
+ foreignKey: string
191
+ },
192
+ foreignKey?: string,
193
+ callback?: (qb: Knex.QueryBuilder) => void
194
+ ): QueryBuilder<TRecord, TResult>
195
+
196
+ whereJoinDoesntHave(
197
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
198
+ table: string,
199
+ relation:
200
+ | string
201
+ | {
202
+ pivotTable: string
203
+ localKey: string
204
+ pivotLocalKey: string
205
+ pivotForeignKey: string
206
+ foreignKey: string
207
+ },
208
+ foreignKey?: string,
209
+ callback?: (qb: Knex.QueryBuilder) => void
210
+ ): QueryBuilder<TRecord, TResult>
211
+
212
+ orWhereJoinDoesntHave(
213
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
214
+ table: string,
215
+ relation:
216
+ | string
217
+ | {
218
+ pivotTable: string
219
+ localKey: string
220
+ pivotLocalKey: string
221
+ pivotForeignKey: string
222
+ foreignKey: string
223
+ },
224
+ foreignKey?: string,
225
+ callback?: (qb: Knex.QueryBuilder) => void
226
+ ): QueryBuilder<TRecord, TResult>
227
+ }
228
+ }
229
+ }
230
+
231
+
232
+
233
+ function getBaseTable(qb: any): string {
234
+ return qb._single?.table
235
+ }
236
+
237
+ function buildWhereHas(
238
+ qb: Knex.QueryBuilder,
239
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
240
+ table: string,
241
+ localKeyOrRelation: any,
242
+ foreignKey?: string,
243
+ callback?: (qb: Knex.QueryBuilder) => void
244
+ ) {
245
+ const baseTable = getBaseTable(qb)
246
+ if (!baseTable) {
247
+ throw new Error("whereHas harus dipanggil setelah table()")
248
+ }
249
+
250
+ qb.select(1).from(table)
251
+
252
+ if (type != "BELONGSTOMANY") {
253
+ qb.whereRaw(`${foreignKey} = ${baseTable}.${localKeyOrRelation}`)
254
+ } else {
255
+ const r = localKeyOrRelation
256
+ qb.join(r.pivotTable, `${r.pivotTable}.${r.pivotForeignKey}`, `${table}.${r.foreignKey}`).whereRaw(`${r.pivotTable}.${r.pivotLocalKey} = ${baseTable}.${r.localKey}`)
257
+ }
258
+
259
+ if (callback) callback(qb)
260
+ }
261
+
262
+
263
+
264
+ knex.QueryBuilder.extend("joinWith", function (
265
+ this: Knex.QueryBuilder,
266
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
267
+ table: string,
268
+ relation:
269
+ | {
270
+ localKey: string
271
+ foreignKey: string
272
+ }
273
+ | {
274
+ pivotTable: string
275
+ localKey: string
276
+ pivotLocalKey: string
277
+ pivotForeignKey: string
278
+ foreignKey: string
279
+ },
280
+ as: string,
281
+ callback?: (qb: Knex.QueryBuilder) => void
282
+ ) {
283
+ const baseTable = getBaseTable(this)
284
+ if (!baseTable) throw new Error("joinWith() must be after table()")
285
+
286
+ let subquery: string | null = null
287
+
288
+ if (type === "BELONGSTO") {
289
+ const r = relation as any
290
+
291
+ subquery = `
292
+ (
293
+ select row_to_json(${table})
294
+ from ${table}
295
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
296
+ limit 1
297
+ )
298
+ `
299
+ }
300
+
301
+ if (type === "HASONE") {
302
+ const r = relation as any
303
+
304
+ subquery = `
305
+ (
306
+ select row_to_json(${table})
307
+ from ${table}
308
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
309
+ limit 1
310
+ )
311
+ `
312
+ }
313
+
314
+ if (type === "HASMANY") {
315
+ const r = relation as any
316
+
317
+ subquery = `
318
+ (
319
+ select coalesce(json_agg(${table}), '[]'::json)
320
+ from ${table}
321
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
322
+ )
323
+ `
324
+ }
325
+
326
+ if (type === "BELONGSTOMANY") {
327
+ const r = relation as any
328
+
329
+ subquery = `
330
+ (
331
+ select coalesce(json_agg(${table}), '[]'::json)
332
+ from ${table}
333
+ inner join ${r.pivotTable}
334
+ on ${r.pivotTable}.${r.pivotForeignKey} = ${table}.${r.foreignKey}
335
+ where ${r.pivotTable}.${r.pivotLocalKey} = ${baseTable}.${r.localKey}
336
+ )
337
+ `
338
+ }
339
+
340
+ if (!subquery) {
341
+ throw new Error(`Unsupported relation type: ${type}`)
342
+ }
343
+
344
+ if (callback) {
345
+ callback(this)
346
+ }
347
+
348
+ return this.select(this.client.raw(`${subquery} as "${as}"`))
349
+ })
350
+
351
+
352
+ knex.QueryBuilder.extend("whereJoinHas", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
353
+ return this.whereExists(function (this: Knex.QueryBuilder) {
354
+ buildWhereHas(
355
+ this,
356
+ type,
357
+ table,
358
+ localKeyOrRelation,
359
+ foreignKey,
360
+ callback
361
+ )
362
+ })
363
+ })
364
+
365
+
366
+ knex.QueryBuilder.extend("orJoinWhereHas", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
367
+ return this.orWhereExists(function (this: Knex.QueryBuilder) {
368
+ buildWhereHas(
369
+ this,
370
+ type,
371
+ table,
372
+ localKeyOrRelation,
373
+ foreignKey,
374
+ callback
375
+ )
376
+ })
377
+ })
378
+
379
+
380
+ knex.QueryBuilder.extend("whereJoinDoesntHave", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
381
+ return this.whereNotExists(function (this: Knex.QueryBuilder) {
382
+ buildWhereHas(
383
+ this,
384
+ type,
385
+ table,
386
+ localKeyOrRelation,
387
+ foreignKey,
388
+ callback
389
+ )
390
+ })
391
+ })
392
+
393
+
394
+ knex.QueryBuilder.extend("orWhereJoinDoesntHave", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
395
+ return this.orWhereNotExists(function (this: Knex.QueryBuilder) {
396
+ buildWhereHas(
397
+ this,
398
+ type,
399
+ table,
400
+ localKeyOrRelation,
401
+ foreignKey,
402
+ callback
403
+ )
404
+ })
405
+ })