tspace-mysql 1.9.4-beta.2 → 1.9.4-beta.4

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 (82) hide show
  1. package/README.md +310 -52
  2. package/dist/cli/dump/db.js +3 -15
  3. package/dist/cli/dump/db.js.map +1 -1
  4. package/dist/cli/index.js +0 -3
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/migrations/make-db.js +1 -1
  7. package/dist/cli/migrations/make-db.js.map +1 -1
  8. package/dist/cli/migrations/make-model.js +1 -6
  9. package/dist/cli/migrations/make-model.js.map +1 -1
  10. package/dist/lib/config/index.d.ts +2 -2
  11. package/dist/lib/config/index.js +14 -5
  12. package/dist/lib/config/index.js.map +1 -1
  13. package/dist/lib/constants/index.d.ts +1 -0
  14. package/dist/lib/constants/index.js +1 -0
  15. package/dist/lib/constants/index.js.map +1 -1
  16. package/dist/lib/core/Abstracts/AbstractBuilder.d.ts +2 -1
  17. package/dist/lib/core/Abstracts/AbstractDB.d.ts +5 -3
  18. package/dist/lib/core/Abstracts/AbstractDB.js.map +1 -1
  19. package/dist/lib/core/Blueprint.d.ts +10 -0
  20. package/dist/lib/core/Blueprint.js +15 -0
  21. package/dist/lib/core/Blueprint.js.map +1 -1
  22. package/dist/lib/core/Builder.d.ts +145 -10
  23. package/dist/lib/core/Builder.js +499 -143
  24. package/dist/lib/core/Builder.js.map +1 -1
  25. package/dist/lib/core/DB.d.ts +130 -196
  26. package/dist/lib/core/DB.js +434 -529
  27. package/dist/lib/core/DB.js.map +1 -1
  28. package/dist/lib/core/Driver/index.d.ts +15 -1
  29. package/dist/lib/core/Driver/index.js +3 -0
  30. package/dist/lib/core/Driver/index.js.map +1 -1
  31. package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.d.ts +14 -0
  32. package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.js +135 -33
  33. package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.js.map +1 -1
  34. package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.d.ts +14 -0
  35. package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.js +28 -0
  36. package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.js.map +1 -1
  37. package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.d.ts +14 -0
  38. package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.js +105 -23
  39. package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.js.map +1 -1
  40. package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.d.ts +16 -1
  41. package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.js +238 -79
  42. package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.js.map +1 -1
  43. package/dist/lib/core/Driver/sqlite/SqliteDriver.js +1 -1
  44. package/dist/lib/core/Driver/sqlite/SqliteDriver.js.map +1 -1
  45. package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.d.ts +17 -0
  46. package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.js +148 -28
  47. package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.js.map +1 -1
  48. package/dist/lib/core/Model.d.ts +243 -33
  49. package/dist/lib/core/Model.js +354 -56
  50. package/dist/lib/core/Model.js.map +1 -1
  51. package/dist/lib/core/Package.d.ts +5 -0
  52. package/dist/lib/core/Package.js +7 -2
  53. package/dist/lib/core/Package.js.map +1 -1
  54. package/dist/lib/core/Pool.js +44 -62
  55. package/dist/lib/core/Pool.js.map +1 -1
  56. package/dist/lib/core/Queue/types.d.ts +1 -0
  57. package/dist/lib/core/Queue/worker-lock.d.ts +17 -0
  58. package/dist/lib/core/Queue/worker-lock.js +30 -0
  59. package/dist/lib/core/Queue/worker-lock.js.map +1 -0
  60. package/dist/lib/core/Queue/worker.d.ts +6 -3
  61. package/dist/lib/core/Queue/worker.js +126 -46
  62. package/dist/lib/core/Queue/worker.js.map +1 -1
  63. package/dist/lib/core/Repository.d.ts +164 -29
  64. package/dist/lib/core/Repository.js +180 -40
  65. package/dist/lib/core/Repository.js.map +1 -1
  66. package/dist/lib/core/Schema.d.ts +5 -2
  67. package/dist/lib/core/Schema.js +111 -3
  68. package/dist/lib/core/Schema.js.map +1 -1
  69. package/dist/lib/core/StateManager.d.ts +17 -0
  70. package/dist/lib/core/StateManager.js +5 -1
  71. package/dist/lib/core/StateManager.js.map +1 -1
  72. package/dist/lib/core/UtilityTypes.d.ts +5 -1
  73. package/dist/lib/core/UtilityTypes.js.map +1 -1
  74. package/dist/lib/types/index.d.ts +56 -24
  75. package/dist/lib/types/repository/index.d.ts +10 -18
  76. package/dist/lib/utils/index.d.ts +6 -2
  77. package/dist/lib/utils/index.js +85 -15
  78. package/dist/lib/utils/index.js.map +1 -1
  79. package/package.json +1 -2
  80. package/dist/cli/dump/table.d.ts +0 -4
  81. package/dist/cli/dump/table.js +0 -41
  82. package/dist/cli/dump/table.js.map +0 -1
@@ -7,12 +7,11 @@ const DB_1 = require("./DB");
7
7
  const StateManager_1 = require("./StateManager");
8
8
  const Join_1 = require("./Join");
9
9
  const constants_1 = require("../constants");
10
- const config_1 = require("../config");
11
10
  const Pool_1 = require("./Pool");
12
11
  class Builder extends AbstractBuilder_1.AbstractBuilder {
13
12
  constructor() {
14
13
  super();
15
- this._initialConnection();
14
+ this._initBuilder();
16
15
  }
17
16
  /**
18
17
  * The 'instance' method is used get instance.
@@ -44,6 +43,16 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
44
43
  database() {
45
44
  return this.$database;
46
45
  }
46
+ useNode(type, options = {}) {
47
+ if (!this.$cluster) {
48
+ throw new Error('.useNode() is only available in cluster mode');
49
+ }
50
+ if (type === 'primary' && options.node !== undefined) {
51
+ throw new Error('The primary node cannot be specified');
52
+ }
53
+ this.$state.set('NODE', { type, node: options.node });
54
+ return this;
55
+ }
47
56
  /**
48
57
  * The 'rowLock' method is used to row level locks (`FOR UPDATE` or `FOR SHARE`) to the query.
49
58
  *
@@ -2302,43 +2311,15 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
2302
2311
  * @returns {Promise<Array>}
2303
2312
  */
2304
2313
  async showSchema(table = String(this.$state.get("TABLE_NAME")), options = {}) {
2305
- const sql = this._queryBuilder().getSchema({
2314
+ const qb = this._queryBuilder();
2315
+ const sql = qb.getSchema({
2306
2316
  database: this.$database,
2307
- table,
2317
+ table: this.$utils.escape(table),
2308
2318
  });
2309
- const raws = await this._queryStatement(sql);
2319
+ const raws = await this.rawQuery(sql);
2310
2320
  if (options.raw)
2311
2321
  return raws;
2312
- const schema = raws.map((r) => {
2313
- const schema = [];
2314
- schema.push(`\`${r.Field}\``);
2315
- schema.push(`${r.Type}`);
2316
- if (r.Nullable === "YES") {
2317
- schema.push(`NULL`);
2318
- }
2319
- if (r.Nullable === "NO") {
2320
- schema.push(`NOT NULL`);
2321
- }
2322
- if (r.Key === "PRI") {
2323
- schema.push(`PRIMARY KEY`);
2324
- }
2325
- if (r.Key === "UNI") {
2326
- schema.push(`UNIQUE`);
2327
- }
2328
- if (r.Default) {
2329
- if (r.Default.includes("IS_CONST:")) {
2330
- schema.push(`DEFAULT ${String(r.Default).replace("IS_CONST:", "")}`);
2331
- }
2332
- else {
2333
- schema.push(`DEFAULT '${r.Default}'`);
2334
- }
2335
- }
2336
- if (r.Extra) {
2337
- schema.push(`${r.Extra.toUpperCase()}`);
2338
- }
2339
- return schema.join(" ");
2340
- });
2341
- return schema;
2322
+ return qb.mapSchema(raws);
2342
2323
  }
2343
2324
  /**
2344
2325
  * The `getSchema` method is used to retrieve the column schema for the database
@@ -2356,11 +2337,15 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
2356
2337
  *
2357
2338
  */
2358
2339
  async getSchema() {
2359
- const tableName = this.$state.get("TABLE_NAME");
2360
- if (tableName == null) {
2340
+ const table = this.$state.get("TABLE_NAME");
2341
+ if (table == null) {
2361
2342
  throw new Error('Unknown table name');
2362
2343
  }
2363
- return await this.showSchema(tableName, { raw: true });
2344
+ const sql = this._queryBuilder().getSchema({
2345
+ database: this.$database,
2346
+ table,
2347
+ });
2348
+ return await this.rawQuery(sql);
2364
2349
  }
2365
2350
  /**
2366
2351
  * The 'getFKs' method is used to get foreign key constraints for the database or a specific table.
@@ -2822,6 +2807,7 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
2822
2807
  password,
2823
2808
  ...others,
2824
2809
  });
2810
+ this.$state.set('BIND', true);
2825
2811
  this.$pool.set(pool.connect());
2826
2812
  return this;
2827
2813
  }
@@ -2858,6 +2844,7 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
2858
2844
  * @returns {this} this
2859
2845
  */
2860
2846
  bind(connection) {
2847
+ this.$state.set('BIND', true);
2861
2848
  this.$pool.set(connection);
2862
2849
  return this;
2863
2850
  }
@@ -2965,24 +2952,20 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
2965
2952
  * @property {boolean?} opts.distinct
2966
2953
  * @returns {promise<Pagination>}
2967
2954
  */
2968
- async pagination(opts) {
2969
- let limit = 15;
2970
- let page = 1;
2955
+ async pagination(opts = {}) {
2956
+ const limit = this.$utils.softNumber(opts.limit ?? 15);
2957
+ const page = this.$utils.softNumber(opts.page ?? 1);
2971
2958
  if (opts?.distinct) {
2972
2959
  this.distinct();
2973
2960
  }
2974
- if (opts != null) {
2975
- limit = opts?.limit || limit;
2976
- page = opts?.page || page;
2977
- }
2978
2961
  const currentPage = page;
2979
2962
  const nextPage = currentPage + 1;
2980
2963
  const prevPage = currentPage - 1 === 0 ? 1 : currentPage - 1;
2981
2964
  const offset = (page - 1) * limit;
2982
- this.limit(limit);
2983
- this.offset(offset);
2984
- let sql = this._queryBuilder().select();
2985
- const result = await this._queryStatement(sql);
2965
+ const result = await this
2966
+ .limit(limit)
2967
+ .offset(offset)
2968
+ .get();
2986
2969
  if (!result.length)
2987
2970
  return {
2988
2971
  meta: {
@@ -3040,9 +3023,82 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
3040
3023
  * @property {boolean?} opts.distinct
3041
3024
  * @returns {promise<Pagination>}
3042
3025
  */
3043
- async paginate(opts) {
3026
+ async paginate(opts = {}) {
3044
3027
  return await this.pagination(opts);
3045
3028
  }
3029
+ /**
3030
+ * The 'cursorPagination' method is used to perform cursor-based pagination
3031
+ * on a set of database query results obtained through the Query Builder.
3032
+ *
3033
+ * It uses cursor-based pagination instead of offset pagination, making it
3034
+ * more efficient when navigating through large datasets.
3035
+ *
3036
+ * @param {Object?} opts query opts
3037
+ * @param {number?} opts.limit number of records per page, default is 15
3038
+ * @param {string?} opts.cursor cursor used to retrieve the next page
3039
+ * @param {boolean?} opts.distinct whether to retrieve distinct records
3040
+ * @returns {Promise<TCursorPagination>}
3041
+ */
3042
+ async cursorPagination(opts = {}) {
3043
+ const limit = this.$utils.softNumber(opts.limit ?? 15);
3044
+ if (opts.distinct) {
3045
+ this.distinct();
3046
+ }
3047
+ let cursor = null;
3048
+ if (opts.cursor) {
3049
+ try {
3050
+ cursor = JSON.parse(Buffer.from(opts.cursor, 'base64').toString('utf8'));
3051
+ }
3052
+ catch {
3053
+ throw new Error('Invalid cursor');
3054
+ }
3055
+ }
3056
+ ;
3057
+ const primaryKey = this.$state.get('PRIMARY_KEY');
3058
+ const results = await this
3059
+ .when(cursor != null, q => q.where(primaryKey, '>', cursor[primaryKey]))
3060
+ .orderBy(primaryKey, 'ASC')
3061
+ .limit(limit + 1)
3062
+ .get();
3063
+ const hasNextPage = results.length > limit;
3064
+ if (hasNextPage) {
3065
+ results.pop();
3066
+ }
3067
+ const lastResult = results[results.length - 1];
3068
+ const nextCursor = hasNextPage && lastResult
3069
+ ? Buffer.from(JSON.stringify({
3070
+ [primaryKey]: lastResult[primaryKey],
3071
+ timestamp: +new Date()
3072
+ })).toString('base64')
3073
+ : null;
3074
+ return {
3075
+ meta: {
3076
+ limit: limit,
3077
+ count: results.length,
3078
+ next: {
3079
+ cursor: nextCursor,
3080
+ has: hasNextPage,
3081
+ }
3082
+ },
3083
+ data: results,
3084
+ };
3085
+ }
3086
+ /**
3087
+ * The 'cursorPaginate' method is used to perform cursor-based pagination
3088
+ * on a set of database query results obtained through the Query Builder.
3089
+ *
3090
+ * It uses cursor-based pagination instead of offset pagination, making it
3091
+ * more efficient when navigating through large datasets.
3092
+ *
3093
+ * @param {Object?} opts query opts
3094
+ * @param {number?} opts.limit number of records per page, default is 15
3095
+ * @param {string?} opts.cursor cursor used to retrieve the next page
3096
+ * @param {boolean?} opts.distinct whether to retrieve distinct records
3097
+ * @returns {Promise<TCursorPagination>}
3098
+ */
3099
+ async cursorPaginate(opts = {}) {
3100
+ return await this.cursorPagination(opts);
3101
+ }
3046
3102
  /**
3047
3103
  *
3048
3104
  * The 'first' method is used to retrieve the first record that matches the query conditions.
@@ -3152,19 +3208,222 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
3152
3208
  return await this.get(cb);
3153
3209
  }
3154
3210
  /**
3155
- * The 'toAsyncIterable' method is used to execute a database query and return the result set as an asynchronous iterable.
3211
+ * The 'explain' method is used to analyze the query execution plan
3212
+ * without executing the query itself.
3156
3213
  *
3157
- * It retrieves multiple records from a database table based on the criteria specified in the query.
3158
- * The optional callback function can be used to modify the generated SQL query before execution.
3214
+ * It retrieves the execution plan from the database and analyzes it
3215
+ * for potential performance issues such as full table scans, filesorts,
3216
+ * and temporary tables.
3217
+ * @param {string?} sql query for explain
3218
+ * @returns {Promise<TExplain>} query execution plan and analysis
3219
+ */
3220
+ async explain(sql) {
3221
+ if (!sql) {
3222
+ sql = this.toSQL();
3223
+ }
3224
+ const LARGE_SCAN_ROWS = 10_000;
3225
+ const qb = this._queryBuilder();
3226
+ const plan = await this.rawQuery(qb.explain(sql)).catch(() => []);
3227
+ const issues = [];
3228
+ const recommendations = [];
3229
+ for (const row of plan) {
3230
+ const text = JSON.stringify(row).toLowerCase();
3231
+ const textRow = Object.values(row)
3232
+ .filter(value => typeof value === 'string')
3233
+ .join(' ');
3234
+ const type = String(row.type ?? '').toLowerCase();
3235
+ const possibleKeys = row.possible_keys;
3236
+ const key = row.key;
3237
+ const rows = /^\d+(?:\.\d+)?$/.test(String(row.rows))
3238
+ ? Number(row.rows)
3239
+ : Number(textRow.match(/\brows=(\d+(?:\.\d+)?)/i)?.[1] ?? 0);
3240
+ const hasPossibleIndex = possibleKeys != null &&
3241
+ String(possibleKeys).trim() !== '';
3242
+ const hasUsedIndex = key != null &&
3243
+ String(key).trim() !== '';
3244
+ const hasFullScan = type === 'all' &&
3245
+ !hasPossibleIndex;
3246
+ if (hasFullScan) {
3247
+ issues.push({
3248
+ type: 'FULL_SCAN',
3249
+ severity: rows > LARGE_SCAN_ROWS ? 'warning' : 'info',
3250
+ message: `A full table scan was detected (${rows.toLocaleString()} rows).`,
3251
+ });
3252
+ recommendations.push('Consider adding an appropriate index for the query conditions.');
3253
+ }
3254
+ if (Number.isFinite(rows) && rows >= LARGE_SCAN_ROWS) {
3255
+ issues.push({
3256
+ type: 'LARGE_SCAN',
3257
+ severity: 'warning',
3258
+ message: `The query is estimated to scan ${rows} rows.`,
3259
+ });
3260
+ recommendations.push('Consider optimizing the query or adding an appropriate index.');
3261
+ }
3262
+ if (text.includes('filesort') ||
3263
+ text.includes('sort')) {
3264
+ issues.push({
3265
+ type: 'FILE_SORT',
3266
+ severity: 'warning',
3267
+ message: 'A sort operation was detected.',
3268
+ });
3269
+ recommendations.push('Consider an index that supports the WHERE and ORDER BY conditions.');
3270
+ }
3271
+ if (text.includes('temporary') ||
3272
+ text.includes('temp table')) {
3273
+ issues.push({
3274
+ type: 'TEMPORARY_TABLE',
3275
+ severity: 'warning',
3276
+ message: 'A temporary table was used.',
3277
+ });
3278
+ recommendations.push('Review GROUP BY, ORDER BY, DISTINCT, and JOIN conditions.');
3279
+ }
3280
+ const hasNoIndex = type === 'all' &&
3281
+ !hasPossibleIndex &&
3282
+ !hasUsedIndex;
3283
+ if (hasNoIndex) {
3284
+ issues.push({
3285
+ type: 'NO_INDEX',
3286
+ severity: 'warning',
3287
+ message: 'No index was used for this operation.',
3288
+ });
3289
+ recommendations.push('Consider adding an index for the query conditions.');
3290
+ }
3291
+ if (hasFullScan &&
3292
+ (text.includes('filter:') ||
3293
+ text.includes('using where'))) {
3294
+ issues.push({
3295
+ type: 'FILTER_AFTER_SCAN',
3296
+ severity: 'warning',
3297
+ message: 'Rows are filtered after scanning the table.',
3298
+ });
3299
+ recommendations.push('Consider an index that can apply the filter earlier.');
3300
+ }
3301
+ }
3302
+ const status = issues.some(issue => issue.severity === 'critical')
3303
+ ? 'critical'
3304
+ : issues.some(issue => issue.severity === 'warning')
3305
+ ? 'warning'
3306
+ : 'ok';
3307
+ return {
3308
+ sql,
3309
+ plan,
3310
+ analysis: {
3311
+ status,
3312
+ issues,
3313
+ recommendations: [...new Set(recommendations)],
3314
+ },
3315
+ };
3316
+ }
3317
+ /**
3318
+ * The 'lazy' method is used to retrieve and process records incrementally
3319
+ * without loading the entire result set into memory at once.
3159
3320
  *
3160
- * @param {Function?} cb callback function return query sql
3161
- * @returns {AsyncIterable<any>}
3321
+ * It retrieves records in batches based on the specified limit and invokes
3322
+ * the callback for each record. The process continues until all records
3323
+ * have been retrieved and processed.
3324
+ *
3325
+ * The optional delayMs can be used to add a delay between each batch query,
3326
+ * which can help reduce database load when processing a large number of records.
3327
+ *
3328
+ * @param {Function} callback callback function that receives each retrieved record
3329
+ * @param {Object?} opts query opts
3330
+ * @param {number?} opts.limit number of records to retrieve per query
3331
+ * @param {number?} opts.offset number of records to skip before processing
3332
+ * @param {number?} opts.delayMs delay in milliseconds between each batch
3333
+ * @returns {Promise<void>}
3162
3334
  */
3163
- async *toAsyncIterable(cb) {
3164
- if (this.$state.get("VOID"))
3335
+ async lazy(callback, opts = {}) {
3336
+ const limit = opts.limit ?? 100;
3337
+ const delayMs = opts.delayMs ?? 0;
3338
+ let offset = opts.offset ?? 0;
3339
+ while (true) {
3340
+ const results = await this
3341
+ .limit(limit)
3342
+ .offset(offset)
3343
+ .get();
3344
+ if (results.length === 0) {
3345
+ break;
3346
+ }
3347
+ for (const result of results) {
3348
+ await callback(result);
3349
+ }
3350
+ if (results.length < limit) {
3351
+ break;
3352
+ }
3353
+ offset += results.length;
3354
+ if (delayMs > 0) {
3355
+ await new Promise(resolve => setTimeout(resolve, delayMs));
3356
+ }
3357
+ }
3358
+ }
3359
+ /**
3360
+ * The 'chunk' method is used to retrieve records from the database in chunks
3361
+ * and process each chunk through a callback function.
3362
+ *
3363
+ * It retrieves records in batches based on the specified limit and invokes
3364
+ * the callback with each batch of records. The process continues until all
3365
+ * records have been retrieved and processed.
3366
+ *
3367
+ * The optional delayMs can be used to add a delay between each batch query,
3368
+ * which can help reduce database load when processing a large number of records.
3369
+ *
3370
+ * @param {Function} callback callback function that receives the retrieved records
3371
+ * @param {Object?} opts query opts
3372
+ * @param {number?} opts.limit number of records to retrieve per chunk
3373
+ * @param {number?} opts.offset number of records to skip before processing
3374
+ * @param {number?} opts.delayMs delay in milliseconds between each batch
3375
+ * @returns {Promise<void>}
3376
+ */
3377
+ async chunk(callback, opts = {}) {
3378
+ const limit = opts.limit ?? 100;
3379
+ const delayMs = opts.delayMs ?? 0;
3380
+ let offset = opts.offset ?? 0;
3381
+ while (true) {
3382
+ const results = await this
3383
+ .limit(limit)
3384
+ .offset(offset)
3385
+ .get();
3386
+ if (results.length === 0) {
3387
+ break;
3388
+ }
3389
+ await callback(results);
3390
+ if (results.length < limit) {
3391
+ break;
3392
+ }
3393
+ offset += results.length;
3394
+ if (delayMs > 0) {
3395
+ await new Promise(resolve => setTimeout(resolve, delayMs));
3396
+ }
3397
+ }
3398
+ }
3399
+ /**
3400
+ * The 'stream' method is used to executes a SQL statement and asynchronously yields result rows.
3401
+ *
3402
+ * @returns {AsyncGenerator<T>} An async generator that yields result rows.
3403
+ *
3404
+ * @example
3405
+ * ```ts
3406
+ * import { DB } from "tspace-mysql";
3407
+ *
3408
+ * for await (const row of new DB('users').stream()) {
3409
+ * console.log(row);
3410
+ * }
3411
+ * ```
3412
+ */
3413
+ async *stream() {
3414
+ if (this.$state.get('VOID')) {
3165
3415
  return yield* [];
3166
- const results = await this.get(cb);
3167
- return yield* results;
3416
+ }
3417
+ const sql = this.toString();
3418
+ if (this.$state.get("DEBUG")) {
3419
+ const startTime = +new Date();
3420
+ const results = await this.$pool.stream(sql);
3421
+ const endTime = +new Date();
3422
+ this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
3423
+ this.$utils.consoleExec(startTime, endTime);
3424
+ return yield* results;
3425
+ }
3426
+ return yield* await this.$pool.stream(sql);
3168
3427
  }
3169
3428
  /**
3170
3429
  * The 'toArray' method is used to execute a database query and retrieve the result set that matches the query conditions.
@@ -3842,14 +4101,14 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
3842
4101
  const result = await this.$pool.query(sql);
3843
4102
  const endTime = +new Date();
3844
4103
  if (this.$state.get("DEBUG")) {
3845
- this.$utils.consoleDebug(sql);
4104
+ this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
3846
4105
  this.$utils.consoleExec(startTime, endTime);
3847
4106
  }
3848
4107
  return result;
3849
4108
  }
3850
4109
  catch (err) {
3851
4110
  if (this.$state.get("DEBUG"))
3852
- this.$utils.consoleDebug(sql);
4111
+ this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
3853
4112
  throw err;
3854
4113
  }
3855
4114
  }
@@ -3859,14 +4118,14 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
3859
4118
  const result = await this.$pool.query(sql);
3860
4119
  const endTime = +new Date();
3861
4120
  if (this.$state.get("DEBUG")) {
3862
- this.$utils.consoleDebug(sql);
4121
+ this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
3863
4122
  this.$utils.consoleExec(startTime, endTime);
3864
4123
  }
3865
4124
  return result;
3866
4125
  }
3867
4126
  catch (err) {
3868
4127
  if (this.$state.get("DEBUG"))
3869
- this.$utils.consoleDebug(sql);
4128
+ this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
3870
4129
  throw err;
3871
4130
  }
3872
4131
  }
@@ -4130,95 +4389,192 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
4130
4389
  ]);
4131
4390
  return this;
4132
4391
  }
4133
- _initialConnection() {
4134
- this.$utils = utils_1.utils;
4135
- this.$pool = (() => {
4136
- if (config_1.Config.CLUSTER) {
4137
- let poolCluster = Pool_1.Pool.clusterConnect();
4138
- if (poolCluster == null) {
4139
- throw new Error("Cluster connection has not been initialized. Please verify your confings");
4392
+ _initClusterPool() {
4393
+ let poolCluster = Pool_1.Pool.clusterConnect();
4394
+ if (poolCluster == null) {
4395
+ throw new Error("Cluster connection has not been initialized. Please verify your confings");
4396
+ }
4397
+ return {
4398
+ query: async (sql) => {
4399
+ // The .bind the cluster context to the query method,
4400
+ // Overriding only the local pool reference.
4401
+ if (poolCluster.primary == null) {
4402
+ const pool = poolCluster;
4403
+ // Reset the node state when the primary node is unavailable,
4404
+ // since it may have been set by the client.
4405
+ this.$state.set('NODE', null);
4406
+ return pool.query(sql);
4140
4407
  }
4141
- return {
4142
- query: async (sql) => {
4143
- const first = sql.trim().split(/\s+/)[0].toUpperCase();
4144
- const isRowLock = Object.values(this.$constants("ROW_LEVEL_LOCK")).some(lock => sql.toUpperCase().includes(lock));
4145
- // Row-level locked SELECTs (e.g. FOR UPDATE, FOR SHARE) acquire database locks,
4146
- // so they are not considered read-only queries and should be handled as write/locking operations.
4147
- const isReaded = [
4148
- this.$constants("SELECT"),
4149
- this.$constants("SHOW"),
4150
- this.$constants("DESCRIBE")
4151
- ].includes(first) && !isRowLock;
4152
- if (isReaded) {
4153
- const length = poolCluster?.slaves?.length ?? 0;
4154
- const random = Math.floor(Math.random() * length);
4155
- return poolCluster?.query != null
4156
- ? poolCluster.query(sql)
4157
- : poolCluster?.slaves[random].query(sql);
4158
- }
4159
- const length = poolCluster?.masters?.length ?? 0;
4160
- const random = Math.floor(Math.random() * length);
4161
- return poolCluster.query != null
4162
- ? poolCluster.query(sql)
4163
- : poolCluster?.masters[random].query(sql);
4164
- },
4165
- get: () => poolCluster,
4166
- set: (conn) => {
4167
- if (conn.database != null) {
4168
- this.$database = conn.database();
4169
- }
4170
- poolCluster = conn;
4171
- return;
4172
- },
4173
- queryBuilder: () => {
4174
- /**
4175
- *
4176
- * queryBuilder can use every nodes
4177
- */
4178
- return poolCluster?.masters == null
4179
- ? poolCluster?.queryBuilder
4180
- : poolCluster?.masters[0]?.queryBuilder;
4181
- },
4182
- transaction: async () => {
4183
- return await poolCluster.masters[0].connection();
4184
- },
4185
- stream: async (sql) => {
4186
- return await poolCluster.masters[0].stream(sql);
4187
- },
4188
- };
4189
- }
4190
- let pool = Pool_1.Pool.connect();
4191
- return {
4192
- stream: async (sql) => {
4193
- return await pool.stream(sql);
4194
- },
4195
- transaction: async () => {
4196
- return await pool.connection();
4197
- },
4198
- query: async (sql) => {
4408
+ const node = this.$state.get('NODE');
4409
+ if (node?.type === 'primary') {
4410
+ this.useNode('primary');
4411
+ return poolCluster.primary.query(sql);
4412
+ }
4413
+ if (node?.type === 'replica') {
4414
+ const length = poolCluster.replicas.length;
4415
+ const nodeIndex = node.node
4416
+ ? node.node - 1
4417
+ : Math.floor(Math.random() * length);
4418
+ const pool = poolCluster
4419
+ .replicas[nodeIndex];
4420
+ if (pool == null) {
4421
+ throw new Error(`Replica node '${nodeIndex + 1}' not found`);
4422
+ }
4423
+ this.useNode('replica', { node: nodeIndex + 1 });
4199
4424
  return pool.query(sql);
4200
- },
4201
- get: () => pool,
4202
- set: (conn) => {
4203
- if (conn.database != null) {
4204
- this.$database = conn.database();
4425
+ }
4426
+ const first = sql
4427
+ .trim()
4428
+ .split(/\s+/)[0].toUpperCase();
4429
+ const isRowLock = Object.values(this.$constants("ROW_LEVEL_LOCK")).some(lock => sql.toUpperCase().includes(lock));
4430
+ // Row-level locked SELECTs (e.g. FOR UPDATE, FOR SHARE) acquire database locks,
4431
+ // so they are not considered read-only queries and should be handled as write/locking operations.
4432
+ const isReaded = [
4433
+ this.$constants("SELECT"),
4434
+ this.$constants("SHOW"),
4435
+ this.$constants("DESCRIBE")
4436
+ ].includes(first) && !isRowLock;
4437
+ if (isReaded) {
4438
+ const length = poolCluster.replicas.length ?? 0;
4439
+ const nodeIndex = Math.floor(Math.random() * length);
4440
+ this.useNode('replica', { node: nodeIndex + 1 });
4441
+ return poolCluster.replicas[nodeIndex].query(sql);
4442
+ }
4443
+ this.useNode('primary');
4444
+ return poolCluster.primary.query(sql);
4445
+ },
4446
+ get: () => poolCluster,
4447
+ set: (conn) => {
4448
+ if (conn.database != null) {
4449
+ this.$database = conn.database();
4450
+ }
4451
+ poolCluster = conn;
4452
+ return;
4453
+ },
4454
+ queryBuilder: () => {
4455
+ // The .bind the cluster context to the query method,
4456
+ // Overriding only the local pool reference.
4457
+ if (poolCluster.primary == null) {
4458
+ const pool = poolCluster;
4459
+ return pool.queryBuilder;
4460
+ }
4461
+ return poolCluster.primary.queryBuilder;
4462
+ },
4463
+ transaction: async () => {
4464
+ // The .bind the cluster context to the query method,
4465
+ // Overriding only the local pool reference.
4466
+ if (poolCluster.primary == null) {
4467
+ const pool = poolCluster;
4468
+ return await pool.connection();
4469
+ }
4470
+ const node = this.$state.get('NODE');
4471
+ if (node?.type === 'primary') {
4472
+ return await poolCluster.primary.connection();
4473
+ }
4474
+ if (node?.type === 'replica') {
4475
+ const length = poolCluster.replicas.length;
4476
+ const nodeIndex = node.node
4477
+ ? node.node - 1
4478
+ : Math.floor(Math.random() * length);
4479
+ const pool = poolCluster
4480
+ .replicas[nodeIndex];
4481
+ if (pool == null) {
4482
+ throw new Error(`Replica node '${nodeIndex + 1}' not found`);
4205
4483
  }
4206
- pool = conn;
4207
- return;
4208
- },
4209
- queryBuilder: () => pool.queryBuilder,
4210
- };
4211
- })();
4212
- this.$state = new StateManager_1.StateManager("default");
4213
- this.$constants = ((name) => {
4214
- if (name == null)
4484
+ return await pool.connection();
4485
+ }
4486
+ return await poolCluster.primary.connection();
4487
+ },
4488
+ stream: async (sql) => {
4489
+ // The .bind the cluster context to the query method,
4490
+ // Overriding only the local pool reference.
4491
+ if (poolCluster.primary == null) {
4492
+ const pool = poolCluster;
4493
+ return pool.stream(sql);
4494
+ }
4495
+ const node = this.$state.get('NODE');
4496
+ if (node?.type === 'primary') {
4497
+ return poolCluster.primary.stream(sql);
4498
+ }
4499
+ if (node?.type === 'replica') {
4500
+ const length = poolCluster.replicas.length;
4501
+ const nodeIndex = node.node
4502
+ ? node.node - 1
4503
+ : Math.floor(Math.random() * length);
4504
+ const pool = poolCluster
4505
+ .replicas[nodeIndex];
4506
+ if (pool == null) {
4507
+ throw new Error(`Replica node '${nodeIndex + 1}' not found`);
4508
+ }
4509
+ return pool.stream(sql);
4510
+ }
4511
+ const first = sql
4512
+ .trim()
4513
+ .split(/\s+/)[0].toUpperCase();
4514
+ const isRowLock = Object.values(this.$constants("ROW_LEVEL_LOCK")).some(lock => sql.toUpperCase().includes(lock));
4515
+ // Row-level locked SELECTs (e.g. FOR UPDATE, FOR SHARE) acquire database locks,
4516
+ // so they are not considered read-only queries and should be handled as write/locking operations.
4517
+ const isReaded = [
4518
+ this.$constants("SELECT"),
4519
+ this.$constants("SHOW"),
4520
+ this.$constants("DESCRIBE")
4521
+ ].includes(first) && !isRowLock;
4522
+ if (isReaded) {
4523
+ const length = poolCluster.replicas.length ?? 0;
4524
+ const nodeIndex = Math.floor(Math.random() * length);
4525
+ this.useNode('replica', { node: nodeIndex + 1 });
4526
+ return poolCluster.replicas[nodeIndex].stream(sql);
4527
+ }
4528
+ this.useNode('primary');
4529
+ return poolCluster.primary.stream(sql);
4530
+ },
4531
+ };
4532
+ }
4533
+ _initSinglePool() {
4534
+ let pool = Pool_1.Pool.connect();
4535
+ return {
4536
+ transaction: async () => {
4537
+ return await pool.connection();
4538
+ },
4539
+ query: async (sql) => {
4540
+ return pool.query(sql);
4541
+ },
4542
+ get: () => pool,
4543
+ set: (conn) => {
4544
+ if (conn.database != null) {
4545
+ this.$database = conn.database();
4546
+ }
4547
+ pool = conn;
4548
+ return;
4549
+ },
4550
+ queryBuilder: () => pool.queryBuilder,
4551
+ stream: async (sql) => {
4552
+ return await pool.stream(sql);
4553
+ },
4554
+ };
4555
+ }
4556
+ _initPool() {
4557
+ return this.$cluster
4558
+ ? this._initClusterPool()
4559
+ : this._initSinglePool();
4560
+ }
4561
+ _initConstants() {
4562
+ return ((name) => {
4563
+ if (name == null) {
4215
4564
  return constants_1.CONSTANTS;
4565
+ }
4216
4566
  if (!Object.prototype.hasOwnProperty.call(constants_1.CONSTANTS, name)) {
4217
- throw new Error(`Not found that constant : '${String(name)}'`);
4567
+ throw new Error(`Constant not found: '${String(name)}'`);
4218
4568
  }
4219
4569
  return constants_1.CONSTANTS[name];
4220
4570
  });
4221
4571
  }
4572
+ _initBuilder() {
4573
+ this.$utils = utils_1.utils;
4574
+ this.$pool = this._initPool();
4575
+ this.$state = new StateManager_1.StateManager("default");
4576
+ this.$constants = this._initConstants();
4577
+ }
4222
4578
  }
4223
4579
  exports.Builder = Builder;
4224
4580
  exports.default = Builder;