@hesed/mysql 0.6.0 → 0.6.1

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/README.md CHANGED
@@ -26,7 +26,7 @@ $ npm install -g @hesed/mysql
26
26
  $ mq COMMAND
27
27
  running command...
28
28
  $ mq (--version)
29
- @hesed/mysql/0.6.0 linux-x64 node-v24.18.0
29
+ @hesed/mysql/0.6.1 linux-x64 node-v24.18.0
30
30
  $ mq --help [COMMAND]
31
31
  USAGE
32
32
  $ mq COMMAND
@@ -82,7 +82,7 @@ EXAMPLES
82
82
  $ mq mysql auth add -p prod
83
83
  ```
84
84
 
85
- _See code: [src/commands/mysql/auth/add.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/add.ts)_
85
+ _See code: [src/commands/mysql/auth/add.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/add.ts)_
86
86
 
87
87
  ## `mq mysql auth delete`
88
88
 
@@ -107,7 +107,7 @@ EXAMPLES
107
107
  $ mq mysql auth delete -p prod
108
108
  ```
109
109
 
110
- _See code: [src/commands/mysql/auth/delete.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/delete.ts)_
110
+ _See code: [src/commands/mysql/auth/delete.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/delete.ts)_
111
111
 
112
112
  ## `mq mysql auth list`
113
113
 
@@ -127,7 +127,7 @@ EXAMPLES
127
127
  $ mq mysql auth list
128
128
  ```
129
129
 
130
- _See code: [src/commands/mysql/auth/list.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/list.ts)_
130
+ _See code: [src/commands/mysql/auth/list.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/list.ts)_
131
131
 
132
132
  ## `mq mysql auth profile`
133
133
 
@@ -152,7 +152,7 @@ EXAMPLES
152
152
  $ mq mysql auth profile --default test
153
153
  ```
154
154
 
155
- _See code: [src/commands/mysql/auth/profile.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/profile.ts)_
155
+ _See code: [src/commands/mysql/auth/profile.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/profile.ts)_
156
156
 
157
157
  ## `mq mysql auth test`
158
158
 
@@ -177,7 +177,7 @@ EXAMPLES
177
177
  $ mq mysql auth test -p prod
178
178
  ```
179
179
 
180
- _See code: [src/commands/mysql/auth/test.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/test.ts)_
180
+ _See code: [src/commands/mysql/auth/test.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/test.ts)_
181
181
 
182
182
  ## `mq mysql auth update`
183
183
 
@@ -211,7 +211,7 @@ EXAMPLES
211
211
  $ mq mysql auth update -p test
212
212
  ```
213
213
 
214
- _See code: [src/commands/mysql/auth/update.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/auth/update.ts)_
214
+ _See code: [src/commands/mysql/auth/update.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/auth/update.ts)_
215
215
 
216
216
  ## `mq mysql databases`
217
217
 
@@ -236,7 +236,7 @@ EXAMPLES
236
236
  $ mq mysql databases -p staging
237
237
  ```
238
238
 
239
- _See code: [src/commands/mysql/databases.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/databases.ts)_
239
+ _See code: [src/commands/mysql/databases.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/databases.ts)_
240
240
 
241
241
  ## `mq mysql describe-table TABLE`
242
242
 
@@ -265,7 +265,7 @@ EXAMPLES
265
265
  $ mq mysql describe-table orders --toon -p prod
266
266
  ```
267
267
 
268
- _See code: [src/commands/mysql/describe-table.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/describe-table.ts)_
268
+ _See code: [src/commands/mysql/describe-table.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/describe-table.ts)_
269
269
 
270
270
  ## `mq mysql explain QUERY`
271
271
 
@@ -294,7 +294,7 @@ EXAMPLES
294
294
  $ mq mysql explain "SELECT * FROM orders JOIN users ON orders.user_id = users.id" --json
295
295
  ```
296
296
 
297
- _See code: [src/commands/mysql/explain.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/explain.ts)_
297
+ _See code: [src/commands/mysql/explain.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/explain.ts)_
298
298
 
299
299
  ## `mq mysql indexes TABLE`
300
300
 
@@ -323,7 +323,7 @@ EXAMPLES
323
323
  $ mq mysql indexes orders --json -p prod
324
324
  ```
325
325
 
326
- _See code: [src/commands/mysql/indexes.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/indexes.ts)_
326
+ _See code: [src/commands/mysql/indexes.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/indexes.ts)_
327
327
 
328
328
  ## `mq mysql query QUERY`
329
329
 
@@ -355,7 +355,7 @@ EXAMPLES
355
355
  $ mq mysql query "DELETE FROM sessions" -p prod --skip-confirmation
356
356
  ```
357
357
 
358
- _See code: [src/commands/mysql/query.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/query.ts)_
358
+ _See code: [src/commands/mysql/query.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/query.ts)_
359
359
 
360
360
  ## `mq mysql tables`
361
361
 
@@ -380,5 +380,5 @@ EXAMPLES
380
380
  $ mq mysql tables -p local
381
381
  ```
382
382
 
383
- _See code: [src/commands/mysql/tables.ts](https://github.com/hesedcasa/mysql/blob/v0.6.0/src/commands/mysql/tables.ts)_
383
+ _See code: [src/commands/mysql/tables.ts](https://github.com/hesedcasa/mysql/blob/v0.6.1/src/commands/mysql/tables.ts)_
384
384
  <!-- commandsstop -->
@@ -1,4 +1,4 @@
1
- import { ApiResult } from '@hesed/plugin-lib';
1
+ import { type ApiResult } from '@hesed/plugin-lib';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  export default class MySQLDescribeTable extends BaseCommand {
4
4
  static args: {
@@ -1,4 +1,4 @@
1
- import { ApiResult } from '@hesed/plugin-lib';
1
+ import { type ApiResult } from '@hesed/plugin-lib';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  export default class MySQLExplain extends BaseCommand {
4
4
  static args: {
@@ -1,4 +1,4 @@
1
- import { ApiResult } from '@hesed/plugin-lib';
1
+ import { type ApiResult } from '@hesed/plugin-lib';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  export default class MySQLIndexes extends BaseCommand {
4
4
  static args: {
@@ -1,4 +1,4 @@
1
- import { ApiResult } from '@hesed/plugin-lib';
1
+ import { type ApiResult } from '@hesed/plugin-lib';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  export default class MySQLQuery extends BaseCommand {
4
4
  static args: {
@@ -1,5 +1,5 @@
1
1
  import type { ConnectionOptions as MySQL2ConnectionOptions } from 'mysql2/promise';
2
- export interface DatabaseProfile {
2
+ export type DatabaseProfile = {
3
3
  database: string;
4
4
  host: string;
5
5
  maxConcurrentQueries?: number;
@@ -8,20 +8,20 @@ export interface DatabaseProfile {
8
8
  queryQueueTimeoutMs?: number;
9
9
  ssl?: boolean;
10
10
  user: string;
11
- }
12
- interface SafetyConfig {
11
+ };
12
+ type SafetyConfig = {
13
13
  blacklistedOperations: string[];
14
14
  defaultLimit: number;
15
15
  maxConcurrentQueries?: number;
16
16
  queryQueueTimeoutMs?: number;
17
17
  requireConfirmationFor: string[];
18
- }
19
- export interface MySQLConfig {
18
+ };
19
+ export type MySQLConfig = {
20
20
  defaultFormat: 'csv' | 'json' | 'table' | 'toon';
21
21
  defaultProfile: string;
22
22
  profiles: Record<string, DatabaseProfile>;
23
23
  safety: SafetyConfig;
24
- }
24
+ };
25
25
  type MySQLConnectionOptions = Pick<MySQL2ConnectionOptions, 'connectTimeout' | 'database' | 'host' | 'multipleStatements' | 'password' | 'port' | 'ssl' | 'user'>;
26
26
  export declare function getMySQLConnectionOptions(config: MySQLConfig, profileName: string): MySQLConnectionOptions;
27
27
  export {};
@@ -1,36 +1,36 @@
1
1
  import type { ApiResult } from '@hesed/plugin-lib';
2
2
  export type OutputFormat = 'csv' | 'json' | 'table' | 'toon';
3
- export interface QueryData {
3
+ export type QueryData = {
4
4
  message?: string;
5
5
  notices?: string;
6
6
  requiresConfirmation?: boolean;
7
7
  result?: unknown;
8
- }
9
- export interface DatabaseListData {
8
+ };
9
+ export type DatabaseListData = {
10
10
  databases: string[];
11
11
  result?: string;
12
- }
13
- export interface TableListData {
12
+ };
13
+ export type TableListData = {
14
14
  result?: string;
15
15
  tables: string[];
16
- }
17
- export interface TableStructureData {
16
+ };
17
+ export type TableStructureData = {
18
18
  result?: string;
19
- structure: Record<string, unknown>[];
20
- }
21
- export interface IndexData {
22
- indexes: Record<string, unknown>[];
19
+ structure: Array<Record<string, unknown>>;
20
+ };
21
+ export type IndexData = {
22
+ indexes: Array<Record<string, unknown>>;
23
23
  result?: string;
24
- }
25
- export interface ExplainData {
26
- plan: Record<string, unknown>[];
24
+ };
25
+ export type ExplainData = {
26
+ plan: Array<Record<string, unknown>>;
27
27
  result?: string;
28
- }
29
- interface ConnectionTestData {
28
+ };
29
+ type ConnectionTestData = {
30
30
  database: string;
31
31
  result?: string;
32
32
  version: string;
33
- }
33
+ };
34
34
  export type QueryResult = ApiResult & {
35
35
  data?: QueryData;
36
36
  };
@@ -52,7 +52,7 @@ export type ExplainResult = ApiResult & {
52
52
  export type ConnectionTestResult = ApiResult & {
53
53
  data?: ConnectionTestData;
54
54
  };
55
- export interface DatabaseUtil {
55
+ export type DatabaseUtil = {
56
56
  closeAll(): Promise<void>;
57
57
  describeTable(profileName: string, table: string, format?: OutputFormat): Promise<TableStructureResult>;
58
58
  executeQuery(profileName: string, query: string, format?: OutputFormat, skipConfirmation?: boolean): Promise<QueryResult>;
@@ -60,5 +60,5 @@ export interface DatabaseUtil {
60
60
  listDatabases(profileName: string): Promise<DatabaseListResult>;
61
61
  listTables(profileName: string): Promise<TableListResult>;
62
62
  showIndexes(profileName: string, table: string, format?: OutputFormat): Promise<IndexResult>;
63
- }
63
+ };
64
64
  export {};
@@ -1,4 +1,5 @@
1
1
  import { encode } from '@toon-format/toon';
2
+ import { Buffer } from 'node:buffer';
2
3
  function formatAsCsv(rows, fields) {
3
4
  if (!rows || rows.length === 0) {
4
5
  return '';
@@ -68,9 +68,10 @@ export async function testDirectConnection(profile) {
68
68
  return result;
69
69
  }
70
70
  export async function closeConnections() {
71
- if (mysqlUtil) {
72
- await mysqlUtil.closeAll();
73
- mysqlUtil = null;
74
- cachedConfig = null;
71
+ if (!mysqlUtil) {
72
+ return;
75
73
  }
74
+ await mysqlUtil.closeAll();
75
+ mysqlUtil = null;
76
+ cachedConfig = null;
76
77
  }
@@ -1,9 +1,9 @@
1
- import type { MySQLConfig } from './config-loader.js';
2
1
  import type { ConnectionTestResult, DatabaseListResult, DatabaseUtil, ExplainResult, IndexResult, OutputFormat, QueryResult, TableListResult, TableStructureResult } from './database.js';
2
+ import { type MySQLConfig } from './config-loader.js';
3
3
  export declare class MySQLUtil implements DatabaseUtil {
4
- private config;
5
- private pools;
6
- private querySlots;
4
+ private readonly config;
5
+ private readonly pools;
6
+ private readonly querySlots;
7
7
  constructor(config: MySQLConfig);
8
8
  closeAll(): Promise<void>;
9
9
  describeTable(profileName: string, table: string, format?: 'json' | 'table' | 'toon'): Promise<TableStructureResult>;
@@ -4,6 +4,10 @@ import { FORMATTERS } from './formatters.js';
4
4
  import { analyzeQuery, applyDefaultLimit, checkBlacklist, getQueryType, requiresConfirmation } from './query-validator.js';
5
5
  const DEFAULT_MAX_CONCURRENT_QUERIES = 5;
6
6
  const DEFAULT_QUEUE_TIMEOUT_MS = 60_000;
7
+ // Formats that must emit only the data payload on stdout.
8
+ const MACHINE_FORMATS = new Set(['csv', 'json', 'toon']);
9
+ // Query types whose result set is rows rather than an OkPacket.
10
+ const READ_QUERY_TYPES = new Set(['DESCRIBE', 'EXPLAIN', 'SELECT', 'SHOW']);
7
11
  export class MySQLUtil {
8
12
  config;
9
13
  pools;
@@ -16,14 +20,16 @@ export class MySQLUtil {
16
20
  async closeAll() {
17
21
  // Reject queued queries first so nothing waits forever on a closed util.
18
22
  for (const slot of this.querySlots.values()) {
19
- for (const waiter of slot.waiting.splice(0)) {
23
+ const { waiting } = slot;
24
+ slot.waiting = [];
25
+ for (const waiter of waiting) {
20
26
  waiter.reject(new Error('Connections were closed while the query was waiting for a free slot'));
21
27
  }
22
28
  }
23
29
  this.querySlots.clear();
24
30
  const pools = [...this.pools.values()];
25
31
  this.pools.clear();
26
- await Promise.allSettled(pools.map((pool) => pool.end()));
32
+ await Promise.allSettled(pools.map(async (pool) => pool.end()));
27
33
  }
28
34
  async describeTable(profileName, table, format = 'table') {
29
35
  try {
@@ -66,7 +72,7 @@ export class MySQLUtil {
66
72
  }
67
73
  // Machine-readable formats must emit only the data payload on stdout, so
68
74
  // analysis warnings and status lines are collected as notices instead.
69
- const machineFormat = format === 'json' || format === 'csv' || format === 'toon';
75
+ const isMachineFormat = MACHINE_FORMATS.has(format);
70
76
  const notices = [];
71
77
  const warnings = analyzeQuery(query);
72
78
  if (warnings.length > 0) {
@@ -83,7 +89,7 @@ export class MySQLUtil {
83
89
  }
84
90
  try {
85
91
  const [rows, fields] = await this.runQuery(profileName, finalQuery);
86
- const isRead = queryType === 'SELECT' || queryType === 'SHOW' || queryType === 'DESCRIBE' || queryType === 'EXPLAIN';
92
+ const isRead = READ_QUERY_TYPES.has(queryType);
87
93
  let data = isRead
88
94
  ? this.formatReadResult(rows, fields, format, notices)
89
95
  : this.formatWriteResult(rows, notices, format);
@@ -95,8 +101,8 @@ export class MySQLUtil {
95
101
  // For machine formats the data is returned alone and notices go to stderr.
96
102
  return {
97
103
  data: {
98
- notices: machineFormat ? notice : undefined,
99
- result: machineFormat ? data : `${notice}\n\n${data}`,
104
+ notices: isMachineFormat ? notice : undefined,
105
+ result: isMachineFormat ? data : `${notice}\n\n${data}`,
100
106
  },
101
107
  success: true,
102
108
  };
@@ -212,7 +218,7 @@ export class MySQLUtil {
212
218
  }
213
219
  // Grants a query slot for the profile, or waits until one frees up. The
214
220
  // returned release callback must be invoked exactly once per acquisition.
215
- acquireQuerySlot(profileName) {
221
+ async acquireQuerySlot(profileName) {
216
222
  const limit = this.getQueryLimit(profileName);
217
223
  let slot = this.querySlots.get(profileName);
218
224
  if (!slot) {
@@ -231,7 +237,7 @@ export class MySQLUtil {
231
237
  };
232
238
  if (state.active < limit) {
233
239
  state.active += 1;
234
- return Promise.resolve(release);
240
+ return release;
235
241
  }
236
242
  const timeoutMs = this.config.profiles[profileName]?.queryQueueTimeoutMs ??
237
243
  this.config.safety.queryQueueTimeoutMs ??
@@ -312,7 +318,7 @@ export class MySQLUtil {
312
318
  async runQuery(profileName, sql) {
313
319
  const release = await this.acquireQuerySlot(profileName);
314
320
  try {
315
- return (await this.getPool(profileName).query(sql));
321
+ return await this.getPool(profileName).query(sql);
316
322
  }
317
323
  finally {
318
324
  release();
@@ -1,16 +1,16 @@
1
- interface BlacklistCheckResult {
1
+ type BlacklistCheckResult = {
2
2
  allowed: boolean;
3
3
  reason?: string;
4
- }
5
- interface ConfirmationCheckResult {
4
+ };
5
+ type ConfirmationCheckResult = {
6
6
  message?: string;
7
7
  required: boolean;
8
- }
9
- interface QueryWarning {
8
+ };
9
+ type QueryWarning = {
10
10
  level: 'info' | 'warning';
11
11
  message: string;
12
12
  suggestion: string;
13
- }
13
+ };
14
14
  export declare function checkBlacklist(query: string, blacklistedOperations: string[]): BlacklistCheckResult;
15
15
  export declare function requiresConfirmation(query: string, confirmationOperations: string[]): ConfirmationCheckResult;
16
16
  export declare function getQueryType(query: string): string;
@@ -26,7 +26,7 @@ export function requiresConfirmation(query, confirmationOperations) {
26
26
  }
27
27
  export function getQueryType(query) {
28
28
  const normalizedQuery = query.trim().toUpperCase();
29
- const firstWord = normalizedQuery.split(/\s+/)[0];
29
+ const firstWord = normalizedQuery.split(/\s+/, 1)[0];
30
30
  const knownTypes = [
31
31
  'SELECT',
32
32
  'INSERT',
@@ -689,5 +689,5 @@
689
689
  ]
690
690
  }
691
691
  },
692
- "version": "0.6.0"
692
+ "version": "0.6.1"
693
693
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@hesed/mysql",
3
3
  "description": "CLI for MySQL database interaction",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "author": "Hesed",
6
6
  "bin": {
7
7
  "mq": "./bin/run.js"
8
8
  },
9
9
  "bugs": "https://github.com/hesedcasa/mysql/issues",
10
10
  "dependencies": {
11
- "@hesed/plugin-lib": "^0.9.0",
11
+ "@hesed/plugin-lib": "^0.12.1",
12
12
  "@inquirer/prompts": "^8.3.0",
13
13
  "@oclif/core": "^4",
14
14
  "@toon-format/toon": "^2.1.0",
@@ -23,11 +23,11 @@
23
23
  "@types/fs-extra": "^11.0.4",
24
24
  "@types/mocha": "^10",
25
25
  "@types/node": "^26",
26
- "@types/sinon": "^21.0.0",
26
+ "@types/sinon": "^22.0.0",
27
27
  "c8": "^11.0.0",
28
28
  "chai": "^6",
29
- "eslint": "^9",
30
- "eslint-config-oclif": "^6",
29
+ "eslint": "^10",
30
+ "eslint-config-oclif": "^7",
31
31
  "eslint-config-prettier": "^10",
32
32
  "esmock": "^2.7.3",
33
33
  "mocha": "^11",
@@ -37,10 +37,11 @@
37
37
  "sinon": "^22.0.0",
38
38
  "ts-node": "^10",
39
39
  "ts-prune": "^0.10.3",
40
- "typescript": "^5"
40
+ "typescript": "^5",
41
+ "typescript-eslint": "^8.67.0"
41
42
  },
42
43
  "engines": {
43
- "node": ">=18.0.0"
44
+ "node": ">=22.13.0"
44
45
  },
45
46
  "files": [
46
47
  "./bin",