@kava/kava-api-core 1.0.0 → 1.0.2

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 (133) hide show
  1. package/.github/workflows/publish.yml +40 -0
  2. package/dist/{auth.util.d.ts → auth/auth.d.ts} +1 -5
  3. package/dist/{auth.util.js → auth/auth.js} +38 -30
  4. package/dist/auth/auth.js.map +1 -0
  5. package/dist/auth/index.d.ts +1 -0
  6. package/dist/auth/index.js +2 -0
  7. package/dist/auth/index.js.map +1 -0
  8. package/dist/{context.util.js → context/context.js} +1 -1
  9. package/dist/context/context.js.map +1 -0
  10. package/dist/context/index.d.ts +1 -0
  11. package/dist/context/index.js +2 -0
  12. package/dist/context/index.js.map +1 -0
  13. package/dist/{controller.util.js → controller/controller.js} +1 -1
  14. package/dist/controller/controller.js.map +1 -0
  15. package/dist/controller/index.d.ts +1 -0
  16. package/dist/controller/index.js +2 -0
  17. package/dist/controller/index.js.map +1 -0
  18. package/dist/{conversion.util.js → conversion/conversion.js} +1 -1
  19. package/dist/conversion/conversion.js.map +1 -0
  20. package/dist/conversion/index.d.ts +1 -0
  21. package/dist/conversion/index.js +2 -0
  22. package/dist/conversion/index.js.map +1 -0
  23. package/dist/{db.util.d.ts → db/db.d.ts} +9 -5
  24. package/dist/{db.util.js → db/db.js} +40 -29
  25. package/dist/db/db.js.map +1 -0
  26. package/dist/db/index.d.ts +1 -0
  27. package/dist/db/index.js +2 -0
  28. package/dist/db/index.js.map +1 -0
  29. package/dist/index.d.ts +14 -13
  30. package/dist/index.js +14 -13
  31. package/dist/index.js.map +1 -1
  32. package/dist/logger/index.d.ts +1 -0
  33. package/dist/logger/index.js +2 -0
  34. package/dist/logger/index.js.map +1 -0
  35. package/dist/{logger.util.js → logger/logger.js} +16 -7
  36. package/dist/logger/logger.js.map +1 -0
  37. package/dist/mail/index.d.ts +1 -0
  38. package/dist/mail/index.js +2 -0
  39. package/dist/mail/index.js.map +1 -0
  40. package/dist/{mail.util.js → mail/mail.js} +1 -1
  41. package/dist/mail/mail.js.map +1 -0
  42. package/dist/middleware/index.d.ts +1 -0
  43. package/dist/middleware/index.js +2 -0
  44. package/dist/middleware/index.js.map +1 -0
  45. package/dist/{middleware.util.js → middleware/middleware.js} +1 -1
  46. package/dist/middleware/middleware.js.map +1 -0
  47. package/dist/model/index.d.ts +1 -0
  48. package/dist/model/index.js +2 -0
  49. package/dist/model/index.js.map +1 -0
  50. package/dist/{model.util.js → model/model.js} +1 -1
  51. package/dist/model/model.js.map +1 -0
  52. package/dist/permission/index.d.ts +1 -0
  53. package/dist/permission/index.js +2 -0
  54. package/dist/permission/index.js.map +1 -0
  55. package/dist/{permission.util.js → permission/permission.js} +1 -1
  56. package/dist/permission/permission.js.map +1 -0
  57. package/dist/registry/index.d.ts +1 -0
  58. package/dist/registry/index.js +2 -0
  59. package/dist/registry/index.js.map +1 -0
  60. package/dist/registry/registry.d.ts +28 -0
  61. package/dist/registry/registry.js +19 -0
  62. package/dist/registry/registry.js.map +1 -0
  63. package/dist/route/index.d.ts +1 -0
  64. package/dist/route/index.js +2 -0
  65. package/dist/route/index.js.map +1 -0
  66. package/dist/{route.util.js → route/route.js} +1 -1
  67. package/dist/route/route.js.map +1 -0
  68. package/dist/storage/index.d.ts +1 -0
  69. package/dist/storage/index.js +2 -0
  70. package/dist/storage/index.js.map +1 -0
  71. package/dist/{storage.util.js → storage/storage.js} +2 -2
  72. package/dist/storage/storage.js.map +1 -0
  73. package/dist/validation/index.d.ts +1 -0
  74. package/dist/validation/index.js +2 -0
  75. package/dist/validation/index.js.map +1 -0
  76. package/dist/{validation.util.js → validation/validation.js} +1 -1
  77. package/dist/validation/validation.js.map +1 -0
  78. package/package.json +2 -2
  79. package/src/{auth.util.ts → auth/auth.ts} +255 -241
  80. package/src/auth/index.ts +1 -0
  81. package/src/{context.util.ts → context/context.ts} +17 -17
  82. package/src/context/index.ts +1 -0
  83. package/src/{controller.util.ts → controller/controller.ts} +236 -236
  84. package/src/controller/index.ts +1 -0
  85. package/src/{conversion.util.ts → conversion/conversion.ts} +64 -64
  86. package/src/conversion/index.ts +1 -0
  87. package/src/{db.util.ts → db/db.ts} +420 -405
  88. package/src/db/index.ts +1 -0
  89. package/src/index.ts +14 -13
  90. package/src/logger/index.ts +1 -0
  91. package/src/{logger.util.ts → logger/logger.ts} +176 -169
  92. package/src/mail/index.ts +1 -0
  93. package/src/{mail.util.ts → mail/mail.ts} +85 -85
  94. package/src/middleware/index.ts +1 -0
  95. package/src/{middleware.util.ts → middleware/middleware.ts} +288 -288
  96. package/src/model/index.ts +1 -0
  97. package/src/{model.util.ts → model/model.ts} +2210 -2210
  98. package/src/permission/index.ts +1 -0
  99. package/src/{permission.util.ts → permission/permission.ts} +136 -136
  100. package/src/registry/index.ts +1 -0
  101. package/src/registry/registry.ts +37 -0
  102. package/src/route/index.ts +1 -0
  103. package/src/{route.util.ts → route/route.ts} +11 -11
  104. package/src/storage/index.ts +1 -0
  105. package/src/{storage.util.ts → storage/storage.ts} +101 -101
  106. package/src/validation/index.ts +1 -0
  107. package/src/{validation.util.ts → validation/validation.ts} +338 -338
  108. package/tsconfig.json +1 -1
  109. package/bun.lock +0 -160
  110. package/dist/auth.util.js.map +0 -1
  111. package/dist/context.util.js.map +0 -1
  112. package/dist/controller.util.js.map +0 -1
  113. package/dist/conversion.util.js.map +0 -1
  114. package/dist/db.util.js.map +0 -1
  115. package/dist/logger.util.js.map +0 -1
  116. package/dist/mail.util.js.map +0 -1
  117. package/dist/middleware.util.js.map +0 -1
  118. package/dist/model.util.js.map +0 -1
  119. package/dist/permission.util.js.map +0 -1
  120. package/dist/route.util.js.map +0 -1
  121. package/dist/storage.util.js.map +0 -1
  122. package/dist/validation.util.js.map +0 -1
  123. /package/dist/{context.util.d.ts → context/context.d.ts} +0 -0
  124. /package/dist/{controller.util.d.ts → controller/controller.d.ts} +0 -0
  125. /package/dist/{conversion.util.d.ts → conversion/conversion.d.ts} +0 -0
  126. /package/dist/{logger.util.d.ts → logger/logger.d.ts} +0 -0
  127. /package/dist/{mail.util.d.ts → mail/mail.d.ts} +0 -0
  128. /package/dist/{middleware.util.d.ts → middleware/middleware.d.ts} +0 -0
  129. /package/dist/{model.util.d.ts → model/model.d.ts} +0 -0
  130. /package/dist/{permission.util.d.ts → permission/permission.d.ts} +0 -0
  131. /package/dist/{route.util.d.ts → route/route.d.ts} +0 -0
  132. /package/dist/{storage.util.d.ts → storage/storage.d.ts} +0 -0
  133. /package/dist/{validation.util.d.ts → validation/validation.d.ts} +0 -0
@@ -1,405 +1,420 @@
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
- })
1
+ import knex, { Knex } from 'knex'
2
+ import { conversion } from '@utils/conversion'
3
+
4
+ // ==============================
5
+ // ## Driver resolver
6
+ // ==============================
7
+ function resolveDriver(conn?: string): 'pg' | 'mysql2' {
8
+ if (!conn) return 'pg'
9
+
10
+ const v = conn.toLowerCase()
11
+ if (['pg', 'pgsql', 'postgres'].includes(v)) return 'pg'
12
+ if (['mysql', 'mysql2'].includes(v)) return 'mysql2'
13
+
14
+ throw new Error(`Unsupported DB_CONNECTION: ${conn}`)
15
+ }
16
+
17
+ // ==============================
18
+ // ## Knex factory
19
+ // ==============================
20
+ function createKnex(config: {
21
+ client ?: string
22
+ host ?: string
23
+ port ?: number
24
+ user ?: string
25
+ password ?: string
26
+ database ?: string
27
+ }) : Knex {
28
+ const client = resolveDriver(config.client)
29
+
30
+ return knex({
31
+ client,
32
+ connection: {
33
+ host : config.host ?? '127.0.0.1',
34
+ port : config.port ?? (client === 'mysql2' ? 3306 : 5432),
35
+ user : config.user ?? 'postgres',
36
+ password : config.password ?? 'password',
37
+ database : config.database ?? 'db_elysia_light',
38
+ },
39
+ pool: { min: 2, max: 10 },
40
+ })
41
+ }
42
+
43
+ // ==============================
44
+ // ## Connection registry
45
+ // ==============================
46
+ const connections: Record<string, Knex> = {}
47
+
48
+ // ==============================
49
+ // ## Default connection
50
+ // ==============================
51
+ const DEFAULT_NAME = 'default'
52
+
53
+ connections[DEFAULT_NAME] = createKnex({
54
+ client : process.env.DB_CONNECTION,
55
+ host : process.env.DB_HOST,
56
+ port : Number(process.env.DB_PORT),
57
+ user : process.env.DB_USERNAME,
58
+ password : process.env.DB_PASSWORD,
59
+ database : process.env.DB_DATABASE,
60
+ })
61
+
62
+ // ==============================
63
+ // ## Export default db
64
+ // ==============================
65
+ export const db: Knex = connections[DEFAULT_NAME]
66
+
67
+ // ==============================
68
+ // ## Named connection (multiple DB)
69
+ // ==============================
70
+ export function useDB(
71
+ name : string,
72
+ config ?: {
73
+ client ?: string
74
+ host ?: string
75
+ port ?: number
76
+ user ?: string
77
+ password ?: string
78
+ database ?: string
79
+ }
80
+ ): Knex {
81
+ if (!connections[name]) {
82
+ if (!config) {
83
+ throw new Error(`DB connection "${name}" not found`)
84
+ }
85
+ connections[name] = createKnex(config)
86
+ }
87
+
88
+ return connections[name]
89
+ }
90
+
91
+ // ==============================
92
+ // ## Close all connections (CLI safe)
93
+ // ==============================
94
+ export async function closeAllDB() {
95
+ await Promise.all( Object.values(connections).map(db => db.destroy()))
96
+ }
97
+
98
+
99
+ // ==============================
100
+ // ## Schema Builder Extensions
101
+ // ==============================
102
+ knex.TableBuilder.extend("softDelete", function(this: Knex.TableBuilder) {
103
+ return this.timestamp("deleted_at").nullable()
104
+ })
105
+
106
+ knex.TableBuilder.extend("foreignIdFor", function(this: Knex.TableBuilder, tableName: string, columnName?: string) {
107
+ const col = columnName || `${conversion.strSingular(tableName)}_id`
108
+ return this.bigInteger(col).unsigned().references("id").inTable(tableName)
109
+ })
110
+
111
+
112
+ declare module "knex" {
113
+ namespace Knex {
114
+ interface TableBuilder {
115
+ softDelete(): Knex.ColumnBuilder
116
+ foreignIdFor(tableName: string, columnName?: string): Knex.ColumnBuilder
117
+ }
118
+ interface QueryBuilder<TRecord = any, TResult = any> {
119
+ where(...args: any[]): this
120
+ orWhere(...args: any[]): this
121
+ whereNot(...args: any[]): this
122
+
123
+ whereNull(...args: any[]): this
124
+ whereNotNull(...args: any[]): this
125
+
126
+ whereIn(...args: any[]): this
127
+ whereNotIn(...args: any[]): this
128
+
129
+ whereBetween(...args: any[]): this
130
+ whereNotBetween(...args: any[]): this
131
+
132
+ whereExists(...args: any[]): this
133
+ whereNotExists(...args: any[]): this
134
+
135
+ // ===== JOIN =====
136
+ join(...args: any[]): this
137
+ leftJoin(...args: any[]): this
138
+ rightJoin(...args: any[]): this
139
+ innerJoin(...args: any[]): this
140
+
141
+ // ===== SELECT =====
142
+ select(...args: any[]): this
143
+ distinct(...args: any[]): this
144
+
145
+ // ===== ORDER / LIMIT =====
146
+ orderBy(...args: any[]): this
147
+ orderByRaw(...args: any[]): this
148
+
149
+ limit(...args: any[]): this
150
+ offset(...args: any[]): this
151
+
152
+ // ===== GROUP =====
153
+ groupBy(...args: any[]): this
154
+ having(...args: any[]): this
155
+
156
+ // ===== RAW =====
157
+ whereRaw(...args: any[]): this
158
+ selectRaw(...args: any[]): this
159
+
160
+ joinWith(
161
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
162
+ table: string,
163
+ relation:
164
+ | {
165
+ localKey: string
166
+ foreignKey: string
167
+ }
168
+ | {
169
+ pivotTable: string
170
+ localKey: string
171
+ pivotLocalKey: string
172
+ pivotForeignKey: string
173
+ foreignKey: string
174
+ },
175
+ as: string,
176
+ callback?: (qb: Knex.QueryBuilder) => void
177
+ ): Knex.QueryBuilder<TRecord, TResult>
178
+
179
+ whereJoinHas(
180
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
181
+ table: string,
182
+ relation:
183
+ | string
184
+ | {
185
+ pivotTable: string
186
+ localKey: string
187
+ pivotLocalKey: string
188
+ pivotForeignKey: string
189
+ foreignKey: string
190
+ },
191
+ foreignKey?: string,
192
+ callback?: (qb: Knex.QueryBuilder) => void
193
+ ): Knex.QueryBuilder<TRecord, TResult>
194
+
195
+ orWhereJoinHas(
196
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
197
+ table: string,
198
+ relation:
199
+ | string
200
+ | {
201
+ pivotTable: string
202
+ localKey: string
203
+ pivotLocalKey: string
204
+ pivotForeignKey: string
205
+ foreignKey: string
206
+ },
207
+ foreignKey?: string,
208
+ callback?: (qb: Knex.QueryBuilder) => void
209
+ ): Knex.QueryBuilder<TRecord, TResult>
210
+
211
+ whereJoinDoesntHave(
212
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
213
+ table: string,
214
+ relation:
215
+ | string
216
+ | {
217
+ pivotTable: string
218
+ localKey: string
219
+ pivotLocalKey: string
220
+ pivotForeignKey: string
221
+ foreignKey: string
222
+ },
223
+ foreignKey?: string,
224
+ callback?: (qb: Knex.QueryBuilder) => void
225
+ ): Knex.QueryBuilder<TRecord, TResult>
226
+
227
+ orWhereJoinDoesntHave(
228
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
229
+ table: string,
230
+ relation:
231
+ | string
232
+ | {
233
+ pivotTable: string
234
+ localKey: string
235
+ pivotLocalKey: string
236
+ pivotForeignKey: string
237
+ foreignKey: string
238
+ },
239
+ foreignKey?: string,
240
+ callback?: (qb: Knex.QueryBuilder) => void
241
+ ): Knex.QueryBuilder<TRecord, TResult>
242
+ }
243
+ }
244
+ }
245
+
246
+
247
+
248
+ function getBaseTable(qb: any): string {
249
+ return qb._single?.table
250
+ }
251
+
252
+ function buildWhereHas(
253
+ qb: Knex.QueryBuilder,
254
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
255
+ table: string,
256
+ localKeyOrRelation: any,
257
+ foreignKey?: string,
258
+ callback?: (qb: Knex.QueryBuilder) => void
259
+ ) {
260
+ const baseTable = getBaseTable(qb)
261
+ if (!baseTable) {
262
+ throw new Error("whereHas harus dipanggil setelah table()")
263
+ }
264
+
265
+ qb.select(1).from(table)
266
+
267
+ if (type != "BELONGSTOMANY") {
268
+ qb.whereRaw(`${foreignKey} = ${baseTable}.${localKeyOrRelation}`)
269
+ } else {
270
+ const r = localKeyOrRelation
271
+ qb.join(r.pivotTable, `${r.pivotTable}.${r.pivotForeignKey}`, `${table}.${r.foreignKey}`).whereRaw(`${r.pivotTable}.${r.pivotLocalKey} = ${baseTable}.${r.localKey}`)
272
+ }
273
+
274
+ if (callback) callback(qb)
275
+ }
276
+
277
+
278
+
279
+ knex.QueryBuilder.extend("joinWith", function (
280
+ this: Knex.QueryBuilder,
281
+ type: "BELONGSTO" | "HASONE" | "HASMANY" | "BELONGSTOMANY",
282
+ table: string,
283
+ relation:
284
+ | {
285
+ localKey: string
286
+ foreignKey: string
287
+ }
288
+ | {
289
+ pivotTable: string
290
+ localKey: string
291
+ pivotLocalKey: string
292
+ pivotForeignKey: string
293
+ foreignKey: string
294
+ },
295
+ as: string,
296
+ callback?: (qb: Knex.QueryBuilder) => void
297
+ ) {
298
+ const baseTable = getBaseTable(this)
299
+ if (!baseTable) throw new Error("joinWith() must be after table()")
300
+
301
+ let subquery: string | null = null
302
+
303
+ if (type === "BELONGSTO") {
304
+ const r = relation as any
305
+
306
+ subquery = `
307
+ (
308
+ select row_to_json(${table})
309
+ from ${table}
310
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
311
+ limit 1
312
+ )
313
+ `
314
+ }
315
+
316
+ if (type === "HASONE") {
317
+ const r = relation as any
318
+
319
+ subquery = `
320
+ (
321
+ select row_to_json(${table})
322
+ from ${table}
323
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
324
+ limit 1
325
+ )
326
+ `
327
+ }
328
+
329
+ if (type === "HASMANY") {
330
+ const r = relation as any
331
+
332
+ subquery = `
333
+ (
334
+ select coalesce(json_agg(${table}), '[]'::json)
335
+ from ${table}
336
+ where ${table}.${r.foreignKey} = ${baseTable}.${r.localKey}
337
+ )
338
+ `
339
+ }
340
+
341
+ if (type === "BELONGSTOMANY") {
342
+ const r = relation as any
343
+
344
+ subquery = `
345
+ (
346
+ select coalesce(json_agg(${table}), '[]'::json)
347
+ from ${table}
348
+ inner join ${r.pivotTable}
349
+ on ${r.pivotTable}.${r.pivotForeignKey} = ${table}.${r.foreignKey}
350
+ where ${r.pivotTable}.${r.pivotLocalKey} = ${baseTable}.${r.localKey}
351
+ )
352
+ `
353
+ }
354
+
355
+ if (!subquery) {
356
+ throw new Error(`Unsupported relation type: ${type}`)
357
+ }
358
+
359
+ if (callback) {
360
+ callback(this)
361
+ }
362
+
363
+ return this.select(this.client.raw(`${subquery} as "${as}"`))
364
+ })
365
+
366
+
367
+ knex.QueryBuilder.extend("whereJoinHas", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
368
+ return this.whereExists(function (this: Knex.QueryBuilder) {
369
+ buildWhereHas(
370
+ this,
371
+ type,
372
+ table,
373
+ localKeyOrRelation,
374
+ foreignKey,
375
+ callback
376
+ )
377
+ })
378
+ })
379
+
380
+
381
+ knex.QueryBuilder.extend("orJoinWhereHas", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
382
+ return this.orWhereExists(function (this: Knex.QueryBuilder) {
383
+ buildWhereHas(
384
+ this,
385
+ type,
386
+ table,
387
+ localKeyOrRelation,
388
+ foreignKey,
389
+ callback
390
+ )
391
+ })
392
+ })
393
+
394
+
395
+ knex.QueryBuilder.extend("whereJoinDoesntHave", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
396
+ return this.whereNotExists(function (this: Knex.QueryBuilder) {
397
+ buildWhereHas(
398
+ this,
399
+ type,
400
+ table,
401
+ localKeyOrRelation,
402
+ foreignKey,
403
+ callback
404
+ )
405
+ })
406
+ })
407
+
408
+
409
+ knex.QueryBuilder.extend("orWhereJoinDoesntHave", function (this: Knex.QueryBuilder, type, table, localKeyOrRelation, foreignKey?, callback? ) {
410
+ return this.orWhereNotExists(function (this: Knex.QueryBuilder) {
411
+ buildWhereHas(
412
+ this,
413
+ type,
414
+ table,
415
+ localKeyOrRelation,
416
+ foreignKey,
417
+ callback
418
+ )
419
+ })
420
+ })