@axiosleo/orm-mysql 0.14.4 → 0.15.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.
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { Command, printer } = require('@axiosleo/cli-tool');
6
+ const { _exists, _copy, _read, _read_json, _mkdir } = require('@axiosleo/cli-tool/src/helper/fs');
7
+
8
+ const SKILL_FILES = [
9
+ 'SKILL.md',
10
+ 'query-building.md',
11
+ 'where-conditions.md',
12
+ 'crud-operations.md',
13
+ 'transactions.md',
14
+ ];
15
+
16
+ const SUPPORTED_TARGETS = ['cursor', 'claude', 'windsurf'];
17
+
18
+ const PKG_NAME = '@axiosleo/orm-mysql';
19
+
20
+ function getTargetDir(cwd, target) {
21
+ const map = {
22
+ cursor: path.join(cwd, '.cursor', 'skills', 'orm-mysql-usage'),
23
+ windsurf: path.join(cwd, '.windsurf', 'skills', 'orm-mysql-usage'),
24
+ };
25
+ return map[target] || null;
26
+ }
27
+
28
+ async function resolveSkillsSource(cwd) {
29
+ const localPkgJson = path.join(cwd, 'node_modules', PKG_NAME, 'package.json');
30
+ let localPkgFound = false;
31
+ let localVersion = null;
32
+
33
+ if (await _exists(localPkgJson)) {
34
+ localPkgFound = true;
35
+ const pkg = await _read_json(localPkgJson);
36
+ localVersion = pkg.version;
37
+ const skillsDir = path.join(cwd, 'node_modules', PKG_NAME, 'skills');
38
+ if (await _exists(skillsDir)) {
39
+ return { dir: skillsDir, version: localVersion, local: true, outdated: false };
40
+ }
41
+ }
42
+
43
+ const fallbackDir = path.join(__dirname, '..', 'skills');
44
+ if (await _exists(fallbackDir)) {
45
+ const fallbackPkg = path.join(__dirname, '..', 'package.json');
46
+ let version = 'unknown';
47
+ if (await _exists(fallbackPkg)) {
48
+ const pkg = await _read_json(fallbackPkg);
49
+ version = pkg.version;
50
+ }
51
+ return {
52
+ dir: fallbackDir,
53
+ version,
54
+ local: false,
55
+ outdated: localPkgFound,
56
+ localVersion,
57
+ };
58
+ }
59
+ return null;
60
+ }
61
+
62
+ class SkillsCommand extends Command {
63
+ constructor() {
64
+ super({
65
+ name: 'skills',
66
+ desc: 'Install or uninstall AI skills for coding assistants (Cursor, Claude Code, Windsurf)',
67
+ });
68
+ this.addOption('install', 'i', 'Install skills for target tool (cursor, claude, windsurf)', 'optional', '');
69
+ this.addOption('uninstall', 'u', 'Uninstall skills for target tool (cursor, claude, windsurf)', 'optional', '');
70
+ }
71
+
72
+ async exec(args, options) {
73
+ const install = options.install;
74
+ const uninstall = options.uninstall;
75
+
76
+ if (!install && !uninstall) {
77
+ this.printUsage();
78
+ return;
79
+ }
80
+
81
+ if (install) {
82
+ await this.install(install);
83
+ } else if (uninstall) {
84
+ await this.uninstall(uninstall);
85
+ }
86
+ }
87
+
88
+ printUsage() {
89
+ printer.println();
90
+ printer.println('Usage:');
91
+ printer.println(' orm-mysql skills --install=<target> Install AI skills');
92
+ printer.println(' orm-mysql skills --uninstall=<target> Uninstall AI skills');
93
+ printer.println();
94
+ printer.println('Supported targets: ' + SUPPORTED_TARGETS.join(', '));
95
+ printer.println();
96
+ printer.println('Examples:');
97
+ printer.println(' npx @axiosleo/orm-mysql skills --install=cursor');
98
+ printer.println(' npx @axiosleo/orm-mysql skills --install=claude');
99
+ printer.println(' npx @axiosleo/orm-mysql skills --uninstall=cursor');
100
+ printer.println();
101
+ }
102
+
103
+ async install(target) {
104
+ if (!SUPPORTED_TARGETS.includes(target)) {
105
+ printer.error(`Unsupported target: "${target}". Supported targets: ${SUPPORTED_TARGETS.join(', ')}`);
106
+ return;
107
+ }
108
+
109
+ const cwd = process.cwd();
110
+ const source = await resolveSkillsSource(cwd);
111
+
112
+ if (!source) {
113
+ printer.error('Could not find skills files. Please reinstall @axiosleo/orm-mysql.');
114
+ return;
115
+ }
116
+
117
+ if (source.local) {
118
+ printer.info(`Found ${PKG_NAME}@${source.version} in node_modules`);
119
+ } else if (source.outdated) {
120
+ printer.warning(`${PKG_NAME}@${source.localVersion} is installed locally but does not include skills files.`);
121
+ printer.warning('Skills files are available since v0.15.1. Please update:');
122
+ printer.warning(` npm install ${PKG_NAME}@latest`);
123
+ printer.println();
124
+ printer.info(`Using skills from npx ${PKG_NAME}@${source.version} instead.`);
125
+ } else {
126
+ printer.warning(`${PKG_NAME} is not installed locally in this project.`);
127
+ printer.warning(`Consider running: npm install ${PKG_NAME}`);
128
+ printer.println();
129
+ }
130
+
131
+ printer.info(`Installing skills for ${target} from ${PKG_NAME}@${source.version}...`);
132
+
133
+ if (target === 'claude') {
134
+ await this.installClaude(cwd, source);
135
+ } else {
136
+ await this.installCopyTarget(cwd, target, source);
137
+ }
138
+ }
139
+
140
+ async installCopyTarget(cwd, target, source) {
141
+ const targetDir = getTargetDir(cwd, target);
142
+ await _mkdir(targetDir);
143
+ await _copy(source.dir, targetDir, true);
144
+ printer.success(`Skills installed to ${path.relative(cwd, targetDir)}/`);
145
+ printer.println();
146
+ printer.println('Files installed:');
147
+ for (const file of SKILL_FILES) {
148
+ printer.println(` - ${file}`);
149
+ }
150
+ printer.println();
151
+ }
152
+
153
+ async installClaude(cwd, source) {
154
+ const claudeFile = path.join(cwd, 'CLAUDE.md');
155
+ const header = `<!-- ${PKG_NAME}@${source.version} skills -->\n`;
156
+ const separator = '\n---\n\n';
157
+
158
+ let content = header;
159
+ for (const file of SKILL_FILES) {
160
+ const filePath = path.join(source.dir, file);
161
+ if (await _exists(filePath)) {
162
+ const fileContent = await _read(filePath);
163
+ content += separator + fileContent.trim() + '\n';
164
+ }
165
+ }
166
+
167
+ if (await _exists(claudeFile)) {
168
+ const existing = await _read(claudeFile);
169
+ if (existing.includes(`<!-- ${PKG_NAME}`) && existing.includes('skills -->')) {
170
+ printer.warning('CLAUDE.md already contains @axiosleo/orm-mysql skills.');
171
+ printer.warning('Please remove the existing skills section first, or run:');
172
+ printer.warning(' npx @axiosleo/orm-mysql skills --uninstall=claude');
173
+ return;
174
+ }
175
+ }
176
+
177
+ fs.appendFileSync(claudeFile, '\n' + content);
178
+ printer.success(`Skills appended to ${path.relative(cwd, claudeFile)}`);
179
+ printer.println();
180
+ }
181
+
182
+ async uninstall(target) {
183
+ if (!SUPPORTED_TARGETS.includes(target)) {
184
+ printer.error(`Unsupported target: "${target}". Supported targets: ${SUPPORTED_TARGETS.join(', ')}`);
185
+ return;
186
+ }
187
+
188
+ const cwd = process.cwd();
189
+
190
+ if (target === 'claude') {
191
+ this.uninstallClaude(cwd);
192
+ return;
193
+ }
194
+
195
+ const targetDir = getTargetDir(cwd, target);
196
+ if (await _exists(targetDir)) {
197
+ fs.rmSync(targetDir, { recursive: true, force: true });
198
+ printer.success(`Skills removed from ${path.relative(cwd, targetDir)}/`);
199
+ } else {
200
+ printer.warning(`No skills found at ${path.relative(cwd, targetDir)}/`);
201
+ }
202
+ }
203
+
204
+ uninstallClaude(cwd) {
205
+ const claudeFile = path.join(cwd, 'CLAUDE.md');
206
+ if (!fs.existsSync(claudeFile)) {
207
+ printer.warning('CLAUDE.md not found.');
208
+ return;
209
+ }
210
+ const content = fs.readFileSync(claudeFile, 'utf8');
211
+ const startMarker = `<!-- ${PKG_NAME}`;
212
+ const idx = content.indexOf(startMarker);
213
+ if (idx === -1) {
214
+ printer.warning('No @axiosleo/orm-mysql skills section found in CLAUDE.md.');
215
+ return;
216
+ }
217
+ const cleaned = content.substring(0, idx).trimEnd() + '\n';
218
+ fs.writeFileSync(claudeFile, cleaned);
219
+ printer.success('Skills section removed from CLAUDE.md');
220
+ }
221
+ }
222
+
223
+ module.exports = SkillsCommand;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiosleo/orm-mysql",
3
- "version": "0.14.4",
3
+ "version": "0.15.1",
4
4
  "description": "MySQL ORM tool",
5
5
  "keywords": [
6
6
  "mysql",
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: orm-mysql-usage
3
+ description: Build MySQL queries, perform CRUD operations, and manage transactions using @axiosleo/orm-mysql. Use when writing database queries, building where conditions, inserting/updating/deleting rows, managing transactions, or working with the ORM query builder in this project.
4
+ ---
5
+
6
+ # @axiosleo/orm-mysql Usage Guide
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @axiosleo/orm-mysql
12
+ ```
13
+
14
+ ## Setup
15
+
16
+ ### Create a Connection
17
+
18
+ ```javascript
19
+ const { createClient, QueryHandler } = require("@axiosleo/orm-mysql");
20
+
21
+ const conn = createClient({
22
+ host: "localhost",
23
+ port: 3306,
24
+ user: "root",
25
+ password: "password",
26
+ database: "my_db",
27
+ });
28
+
29
+ const db = new QueryHandler(conn);
30
+ ```
31
+
32
+ ### Create a Connection Pool (recommended for production)
33
+
34
+ ```javascript
35
+ const { createPool, QueryHandler } = require("@axiosleo/orm-mysql");
36
+
37
+ const pool = createPool({
38
+ host: "localhost",
39
+ port: 3306,
40
+ user: "root",
41
+ password: "password",
42
+ database: "my_db",
43
+ connectionLimit: 10,
44
+ });
45
+
46
+ const db = new QueryHandler(pool);
47
+ ```
48
+
49
+ ### Using MySQLClient
50
+
51
+ ```javascript
52
+ const { MySQLClient } = require("@axiosleo/orm-mysql");
53
+
54
+ const client = new MySQLClient({
55
+ host: "localhost",
56
+ port: 3306,
57
+ user: "root",
58
+ password: "password",
59
+ database: "my_db",
60
+ }, null, "pool"); // "default" | "promise" | "pool"
61
+
62
+ const rows = await client.table("users").select();
63
+ await client.close();
64
+ ```
65
+
66
+ ## Class Hierarchy
67
+
68
+ ```
69
+ QueryCondition -- where clauses (where, whereIn, whereLike, whereBetween...)
70
+ └── Query -- query building (table, attr, join, orderBy, limit, page...)
71
+ └── QueryOperator -- execution (select, find, insert, update, delete...)
72
+ └── TransactionOperator -- adds append() for row locking
73
+ ```
74
+
75
+ - `QueryHandler` wraps a connection/pool and creates `QueryOperator` via `.table(name)`
76
+ - `TransactionHandler` wraps a promise connection and creates `TransactionOperator` via `.table(name)`
77
+
78
+ ## Quick Start
79
+
80
+ ```javascript
81
+ const db = new QueryHandler(conn);
82
+
83
+ // SELECT
84
+ const users = await db.table("users")
85
+ .where("age", ">", 18)
86
+ .orderBy("name", "asc")
87
+ .limit(10)
88
+ .select("id", "name", "age");
89
+
90
+ // INSERT
91
+ await db.table("users").insert({ name: "Joe", age: 25 });
92
+
93
+ // UPDATE
94
+ await db.table("users").where("id", 1).update({ age: 26 });
95
+
96
+ // DELETE
97
+ await db.table("users").where("id", 1).delete();
98
+
99
+ // COUNT
100
+ const total = await db.table("users").where("age", ">", 18).count();
101
+ // IMPORTANT: for paginated lists, reuse the SAME builder for count() and select() -- see pagination.md
102
+
103
+ // FIND single row
104
+ const user = await db.table("users").where("id", 1).find();
105
+ ```
106
+
107
+ ## Dry Run with notExec()
108
+
109
+ Call `notExec()` before any CRUD method to get a `Builder` object with `.sql` and `.values` instead of executing:
110
+
111
+ ```javascript
112
+ const builder = await db.table("users")
113
+ .where("age", ">", 18)
114
+ .notExec()
115
+ .select("id", "name");
116
+
117
+ console.log(builder.sql); // "SELECT `id`, `name` FROM `users` WHERE `age` > ?"
118
+ console.log(builder.values); // [18]
119
+ ```
120
+
121
+ ## Reference Files
122
+
123
+ | Scenario | File |
124
+ |----------|------|
125
+ | Building queries (table, join, orderBy, limit, groupBy, attr) | [query-building.md](query-building.md) |
126
+ | Where conditions (where, whereIn, whereLike, whereBetween...) | [where-conditions.md](where-conditions.md) |
127
+ | CRUD operations (select, find, count, insert, update, delete, incrBy, upsertRow) | [crud-operations.md](crud-operations.md) |
128
+ | Pagination (reuse the same builder for count() and select(), avoid duplicated where clauses) | [pagination.md](pagination.md) |
129
+ | Transactions (beginTransaction, commit, rollback, FOR UPDATE) | [transactions.md](transactions.md) |
130
+
131
+ ## Hooks
132
+
133
+ Register pre/post hooks for query operations:
134
+
135
+ ```javascript
136
+ const { Hook } = require("@axiosleo/orm-mysql");
137
+
138
+ Hook.pre(async (options) => {
139
+ console.log("Before:", options.operator, options.tables);
140
+ }, { table: "users", opt: "insert" });
141
+
142
+ Hook.post(async (options, result) => {
143
+ console.log("After:", result);
144
+ }, { table: "users", opt: "insert" });
145
+ ```
146
+
147
+ ## Schema Helpers
148
+
149
+ ```javascript
150
+ const exists = await db.existTable("users");
151
+ const dbExists = await db.existDatabase("my_db");
152
+ const fields = await db.getTableFields("my_db", "users", "COLUMN_NAME", "DATA_TYPE");
153
+ ```
154
+
155
+ ## Raw SQL
156
+
157
+ ```javascript
158
+ const result = await db.query({ sql: "SELECT * FROM users WHERE id = ?", values: [1] });
159
+ ```
@@ -0,0 +1,225 @@
1
+ # CRUD Operations
2
+
3
+ The `QueryOperator` class provides all execution methods. You get an instance via `db.table("name")`.
4
+
5
+ All CRUD methods return a `Promise`. Write operations resolve to `MySQLQueryResult` (OkPacket | ResultSetHeader). If `notExec()` was called, they resolve to a `Builder` object instead.
6
+
7
+ ## Read Operations
8
+
9
+ ### select(...attrs)
10
+
11
+ Returns an array of rows. Optionally pass column names to override previously set `attr()`.
12
+
13
+ ```javascript
14
+ const rows = await db.table("users").select();
15
+ const rows = await db.table("users").select("id", "name", "email");
16
+
17
+ // With TypeScript generics
18
+ interface User { id: number; name: string; email: string; }
19
+ const users = await db.table("users").select<User>("id", "name", "email");
20
+ ```
21
+
22
+ ### find()
23
+
24
+ Returns a single row (the first match). Automatically applies `LIMIT 1`.
25
+
26
+ ```javascript
27
+ const user = await db.table("users").where("id", 1).find();
28
+
29
+ // With TypeScript generics
30
+ const user = await db.table("users").where("id", 1).find<User>();
31
+ ```
32
+
33
+ ### count()
34
+
35
+ Returns the total number of matching rows as a number.
36
+
37
+ ```javascript
38
+ const total = await db.table("users").where("status", "active").count();
39
+ // total is a number
40
+ ```
41
+
42
+ For paginated list endpoints, do NOT create a separate count builder with duplicated `where` conditions. Reuse the same builder for both `count()` and `select()` -- `count()` ignores `attrs`/`limit`/`offset`/`orderBy`, and `select()` resets the operator. See [pagination.md](pagination.md) for the full pattern.
43
+
44
+ ### explain(operator)
45
+
46
+ Returns the MySQL EXPLAIN result for the query.
47
+
48
+ ```javascript
49
+ const plan = await db.table("users")
50
+ .where("status", "active")
51
+ .explain("select");
52
+ // plan is ExplainResult[]
53
+ ```
54
+
55
+ ## Write Operations
56
+
57
+ ### insert(row)
58
+
59
+ Insert a single row.
60
+
61
+ ```javascript
62
+ const result = await db.table("users").insert({
63
+ name: "Joe",
64
+ age: 25,
65
+ email: "joe@example.com",
66
+ });
67
+ // result.insertId -- the auto-increment ID
68
+ // result.affectedRows -- number of rows inserted
69
+ ```
70
+
71
+ ### Insert with ON DUPLICATE KEY UPDATE
72
+
73
+ Use `keys()` to specify unique columns. If a duplicate is found, the operation becomes an update.
74
+
75
+ ```javascript
76
+ const result = await db.table("users").keys("email").insert({
77
+ email: "joe@example.com",
78
+ name: "Joe Updated",
79
+ age: 26,
80
+ });
81
+ ```
82
+
83
+ ### insertAll(rows)
84
+
85
+ Insert multiple rows. Returns an array of results.
86
+
87
+ ```javascript
88
+ const results = await db.table("users").insertAll([
89
+ { name: "Alice", age: 30 },
90
+ { name: "Bob", age: 28 },
91
+ { name: "Charlie", age: 35 },
92
+ ]);
93
+ // results is MySQLQueryResult[]
94
+ ```
95
+
96
+ ### update(row)
97
+
98
+ Update rows matching the current where conditions.
99
+
100
+ ```javascript
101
+ const result = await db.table("users")
102
+ .where("id", 1)
103
+ .update({ name: "Joe Updated", age: 26 });
104
+ // result.affectedRows -- number of rows updated
105
+ // result.changedRows -- number of rows actually changed
106
+ ```
107
+
108
+ ### delete(id?, index_field_name?)
109
+
110
+ Delete rows. Can be called with conditions or directly with an ID.
111
+
112
+ ```javascript
113
+ // Delete by conditions
114
+ const result = await db.table("users").where("status", "banned").delete();
115
+
116
+ // Delete by primary key (defaults to "id" field)
117
+ const result = await db.table("users").delete(1);
118
+
119
+ // Delete by a custom index field
120
+ const result = await db.table("users").delete(1, "user_id");
121
+ ```
122
+
123
+ ### incrBy(attr, increment?)
124
+
125
+ Increment a column value atomically. Default increment is 1.
126
+
127
+ ```javascript
128
+ // Increment by 1 (default)
129
+ await db.table("users").where("id", 1).incrBy("login_count");
130
+
131
+ // Increment by a specific number
132
+ await db.table("products").where("id", 1).incrBy("views", 5);
133
+
134
+ // Increment by string value
135
+ await db.table("users").where("id", 1).incrBy("score", "10");
136
+
137
+ // Conditional increment with callback
138
+ await db.table("users").where("id", 1).incrBy("error_count", (current) => {
139
+ return shouldIncrement ? 1 : 0;
140
+ });
141
+ ```
142
+
143
+ ### upsertRow(row, condition)
144
+
145
+ Insert a row or update it if a matching row exists based on the condition.
146
+
147
+ ```javascript
148
+ // Using QueryCondition
149
+ const condition = new QueryCondition();
150
+ condition.where("email", "joe@example.com");
151
+
152
+ await db.table("users").upsertRow(
153
+ { email: "joe@example.com", name: "Joe", age: 25 },
154
+ condition
155
+ );
156
+ ```
157
+
158
+ ## Dry Run with notExec()
159
+
160
+ Call `notExec()` before any CRUD method to get the generated SQL without executing it. Returns a `Builder` with `.sql` and `.values`.
161
+
162
+ ```javascript
163
+ const builder = await db.table("users")
164
+ .where("status", "active")
165
+ .orderBy("name", "asc")
166
+ .limit(10)
167
+ .notExec()
168
+ .select("id", "name");
169
+
170
+ console.log(builder.sql); // The generated SQL string
171
+ console.log(builder.values); // The parameter values array
172
+ ```
173
+
174
+ This works with all CRUD methods:
175
+
176
+ ```javascript
177
+ const insertBuilder = await db.table("users")
178
+ .notExec()
179
+ .insert({ name: "Joe", age: 25 });
180
+
181
+ const updateBuilder = await db.table("users")
182
+ .where("id", 1)
183
+ .notExec()
184
+ .update({ age: 26 });
185
+
186
+ const deleteBuilder = await db.table("users")
187
+ .where("id", 1)
188
+ .notExec()
189
+ .delete();
190
+ ```
191
+
192
+ ## Complete Example
193
+
194
+ ```javascript
195
+ const db = new QueryHandler(pool);
196
+
197
+ // Create user
198
+ const insertResult = await db.table("users").insert({
199
+ name: "Joe",
200
+ email: "joe@example.com",
201
+ age: 25,
202
+ });
203
+ const userId = insertResult.insertId;
204
+
205
+ // Read back
206
+ const user = await db.table("users").where("id", userId).find();
207
+
208
+ // Update
209
+ await db.table("users").where("id", userId).update({ age: 26 });
210
+
211
+ // Increment login count
212
+ await db.table("users").where("id", userId).incrBy("login_count");
213
+
214
+ // Count active users
215
+ const activeCount = await db.table("users").where("status", "active").count();
216
+
217
+ // Bulk insert
218
+ await db.table("logs").insertAll([
219
+ { user_id: userId, action: "login" },
220
+ { user_id: userId, action: "view_profile" },
221
+ ]);
222
+
223
+ // Delete
224
+ await db.table("sessions").where("expired_at", "<", new Date()).delete();
225
+ ```