@shadow-dev/orm 2.0.0 → 2.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.
package/README.md CHANGED
@@ -1,118 +1,118 @@
1
- # ShadowORM
2
-
3
- > 🧩 Lightweight, type-safe MySQL ORM for ShadowCore projects.
4
- > ShadowORM is built for **modularity**, **security**, and **runtime schema sync** ΓÇö perfect for bots, services, or web apps using ShadowCore.
5
-
6
- ![npm version](https://img.shields.io/npm/v/@shadow-dev/orm?style=flat-square)
7
- ![license](https://img.shields.io/github/license/Shadows-Development/ShadowORM?style=flat-square)
8
-
9
- ---
10
-
11
- ## 🔍 Overview
12
-
13
- ShadowORM is a minimalist ORM that offers:
14
-
15
- - ✅ **Type-safe models** using generics
16
- - ✅ **Automatic schema synchronization** (no migration needed)
17
- - ✅ **JSON + Date normalization**
18
- - ✅ **Relational support** with foreign keys
19
- - ✅ **No decorators, no reflection, no magic**
20
-
21
- ItΓÇÖs designed to work cleanly alongside ShadowCore but can also be used standalone in any Node.js TypeScript project.
22
-
23
- ---
24
-
25
- ## 📦 Installation
26
-
27
- ```bash
28
- npm install @shadow-dev/orm mysql2
29
- ```
30
-
31
- ---
32
-
33
- ## 🛠 Usage Example
34
-
35
- ```ts
36
- import { Model, Repository, initDatabase, registerModel } from "@shadow-dev/orm";
37
-
38
- const Ticket = new Model<{
39
- id: string;
40
- type: "support" | "report";
41
- data: { message: string };
42
- createdAt: Date;
43
- }>("tickets", {
44
- id: "string",
45
- type: "string",
46
- data: "json",
47
- createdAt: "datetime"
48
- });
49
-
50
- initDatabase({
51
- host: "localhost",
52
- user: "root",
53
- password: "password",
54
- database: "mydb"
55
- });
56
-
57
- registerModel(Ticket);
58
-
59
- // Auto-create table on startup
60
- await syncSchema();
61
-
62
- const tickets = new Repository(Ticket);
63
-
64
-
65
- // Use it
66
- await tickets.create({
67
- id: "ticket-001",
68
- type: "support",
69
- data: { message: "Help me!" },
70
- createdAt: new Date()
71
- });
72
- ```
73
-
74
- ---
75
-
76
- ## 🧠 Schema Types
77
-
78
- ShadowORM supports:
79
-
80
- | Type | SQL Equivalent |
81
- |---------------------------|-------------------|
82
- | `string` | `VARCHAR(255)` |
83
- | `number` | `INT` |
84
- | `boolean` | `BOOLEAN` |
85
- | `json` | `LONGTEXT` |
86
- | `datetime` | `DATETIME` |
87
- | `FOREIGN_KEY:<tbl.col>` | Foreign key ref |
88
-
89
- ---
90
-
91
- ## 🧱 Roadmap
92
-
93
- - [x] CRUD repository
94
- - [x] Relational schema support
95
- - [x] Automatic schema sync
96
- - [ ] CLI (optional)
97
- - [ ] Migrations (optional)
98
- - [ ] Postgres support (maybe)
99
-
100
- ---
101
-
102
- ## 📖 Documentation
103
-
104
- 📚 Docs are coming soon and will be available on the ShadowCore documentation site:
105
- ➡️ [docs.shadowdevelopment.net](https://docs.shadowdevelopment.net)
106
-
107
- ---
108
-
109
- ## 🏢 Project Ownership
110
-
111
- ShadowORM is officially developed and maintained under [Shadow Development LLC](https://shadowdevelopment.net).
112
-
113
- ---
114
-
115
- ## 📜 License
116
-
117
- Licensed under the **GNU General Public License v3.0**
118
- See the [LICENSE](LICENSE) file for details.
1
+ # ShadowORM
2
+
3
+ > 🧩 Lightweight, type-safe MySQL ORM for ShadowCore projects.
4
+ > ShadowORM is built for **modularity**, **security**, and **runtime schema sync** ΓÇö perfect for bots, services, or web apps using ShadowCore.
5
+
6
+ ![npm version](https://img.shields.io/npm/v/@shadow-dev/orm?style=flat-square)
7
+ ![license](https://img.shields.io/github/license/Shadows-Development/ShadowORM?style=flat-square)
8
+
9
+ ---
10
+
11
+ ## 🔍 Overview
12
+
13
+ ShadowORM is a minimalist ORM that offers:
14
+
15
+ - ✅ **Type-safe models** using generics
16
+ - ✅ **Automatic schema synchronization** (no migration needed)
17
+ - ✅ **JSON + Date normalization**
18
+ - ✅ **Relational support** with foreign keys
19
+ - ✅ **No decorators, no reflection, no magic**
20
+
21
+ ItΓÇÖs designed to work cleanly alongside ShadowCore but can also be used standalone in any Node.js TypeScript project.
22
+
23
+ ---
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ npm install @shadow-dev/orm mysql2
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🛠 Usage Example
34
+
35
+ ```ts
36
+ import { Model, Repository, initDatabase, registerModel } from "@shadow-dev/orm";
37
+
38
+ const Ticket = new Model<{
39
+ id: string;
40
+ type: "support" | "report";
41
+ data: { message: string };
42
+ createdAt: Date;
43
+ }>("tickets", {
44
+ id: "string",
45
+ type: "string",
46
+ data: "json",
47
+ createdAt: "datetime"
48
+ });
49
+
50
+ initDatabase({
51
+ host: "localhost",
52
+ user: "root",
53
+ password: "password",
54
+ database: "mydb"
55
+ });
56
+
57
+ registerModel(Ticket);
58
+
59
+ // Auto-create table on startup
60
+ await syncSchema();
61
+
62
+ const tickets = new Repository(Ticket);
63
+
64
+
65
+ // Use it
66
+ await tickets.create({
67
+ id: "ticket-001",
68
+ type: "support",
69
+ data: { message: "Help me!" },
70
+ createdAt: new Date()
71
+ });
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 🧠 Schema Types
77
+
78
+ ShadowORM supports:
79
+
80
+ | Type | SQL Equivalent |
81
+ |---------------------------|-------------------|
82
+ | `string` | `VARCHAR(255)` |
83
+ | `number` | `INT` |
84
+ | `boolean` | `BOOLEAN` |
85
+ | `json` | `LONGTEXT` |
86
+ | `datetime` | `DATETIME` |
87
+ | `FOREIGN_KEY:<tbl.col>` | Foreign key ref |
88
+
89
+ ---
90
+
91
+ ## 🧱 Roadmap
92
+
93
+ - [x] CRUD repository
94
+ - [x] Relational schema support
95
+ - [x] Automatic schema sync
96
+ - [ ] CLI (optional)
97
+ - [ ] Migrations (optional)
98
+ - [ ] Postgres support (maybe)
99
+
100
+ ---
101
+
102
+ ## 📖 Documentation
103
+
104
+ 📚 Docs are coming soon and will be available on the ShadowCore documentation site:
105
+ ➡️ [docs.shadowdevelopment.net](https://docs.shadowdevelopment.net)
106
+
107
+ ---
108
+
109
+ ## 🏢 Project Ownership
110
+
111
+ ShadowORM is officially developed and maintained under [Shadow Development LLC](https://shadowdevelopment.net).
112
+
113
+ ---
114
+
115
+ ## 📜 License
116
+
117
+ Licensed under the **GNU General Public License v3.0**
118
+ See the [LICENSE](LICENSE) file for details.
@@ -66,12 +66,12 @@ export async function transaction(fn) {
66
66
  /* Migrations */
67
67
  /* ---------------------------------- */
68
68
  async function ensureMigrationTable() {
69
- await exec(`
70
- CREATE TABLE IF NOT EXISTS shadoworm_migrations (
71
- id VARCHAR(255) PRIMARY KEY,
72
- name VARCHAR(255) NOT NULL,
73
- executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
74
- );
69
+ await exec(`
70
+ CREATE TABLE IF NOT EXISTS shadoworm_migrations (
71
+ id VARCHAR(255) PRIMARY KEY,
72
+ name VARCHAR(255) NOT NULL,
73
+ executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
74
+ );
75
75
  `);
76
76
  }
77
77
  async function loadMigrations(dir) {
@@ -3,7 +3,7 @@ export interface BaseSchema {
3
3
  data?: any;
4
4
  createdAt?: Date;
5
5
  }
6
- export type SimpleFieldType = "string" | "int" | "float" | "boolean" | "json" | "datetime";
6
+ export type SimpleFieldType = "string" | "text" | "mediumtext" | "longtext" | "int" | "bigint" | "float" | "double" | "decimal" | "boolean" | "date" | "time" | "datetime" | "timestamp" | "json" | "binary" | "varbinary" | "uuid";
7
7
  export interface FieldOptions {
8
8
  type: SimpleFieldType;
9
9
  pk?: boolean;
@@ -16,10 +16,10 @@ export class Repository {
16
16
  const keys = this.getInsertableKeys(data);
17
17
  if (keys.length === 0)
18
18
  throw new Error("create(): empty data");
19
- const sql = `
20
- INSERT INTO \`${this.model.name}\`
21
- (${keys.map(k => `\`${k}\``).join(",")})
22
- VALUES (${keys.map(() => "?").join(",")})
19
+ const sql = `
20
+ INSERT INTO \`${this.model.name}\`
21
+ (${keys.map(k => `\`${k}\``).join(",")})
22
+ VALUES (${keys.map(() => "?").join(",")})
23
23
  `;
24
24
  const values = keys.map(k => this.normalizeWriteValue(k, data[k]));
25
25
  const [res] = await getPool().execute(sql, values);
@@ -39,10 +39,10 @@ export class Repository {
39
39
  .map(() => `(${keys.map(() => "?").join(",")})`)
40
40
  .join(",");
41
41
  const values = rows.flatMap(row => keys.map(k => this.normalizeWriteValue(k, row[k])));
42
- const sql = `
43
- INSERT INTO \`${this.model.name}\`
44
- (${keys.map(k => `\`${k}\``).join(",")})
45
- VALUES ${placeholders}
42
+ const sql = `
43
+ INSERT INTO \`${this.model.name}\`
44
+ (${keys.map(k => `\`${k}\``).join(",")})
45
+ VALUES ${placeholders}
46
46
  `;
47
47
  const [res] = await getPool().execute(sql, values);
48
48
  const pk = this.getPrimaryKeyField();
@@ -59,10 +59,10 @@ export class Repository {
59
59
  .map(() => `(${keys.map(() => "?").join(",")})`)
60
60
  .join(",");
61
61
  const values = rows.flatMap(row => keys.map(k => this.normalizeWriteValue(k, row[k])));
62
- const sql = `
63
- INSERT INTO \`${this.model.name}\`
64
- (${keys.map(k => `\`${k}\``).join(",")})
65
- VALUES ${placeholders}
62
+ const sql = `
63
+ INSERT INTO \`${this.model.name}\`
64
+ (${keys.map(k => `\`${k}\``).join(",")})
65
+ VALUES ${placeholders}
66
66
  `;
67
67
  const [res] = await getPool().execute(sql, values);
68
68
  return res.affectedRows;
@@ -100,9 +100,9 @@ export class Repository {
100
100
  return [];
101
101
  const pk = this.getPrimaryKeyField();
102
102
  const placeholders = ids.map(() => "?").join(",");
103
- const query = `
104
- SELECT * FROM \`${this.model.name}\`
105
- WHERE \`${String(pk)}\` IN (${placeholders})
103
+ const query = `
104
+ SELECT * FROM \`${this.model.name}\`
105
+ WHERE \`${String(pk)}\` IN (${placeholders})
106
106
  `;
107
107
  const [rows] = await getPool().execute(query, ids);
108
108
  return rows;
@@ -120,10 +120,10 @@ export class Repository {
120
120
  const setClause = setKeys.map(k => `\`${k}\` = ?`).join(", ");
121
121
  const setValues = setKeys.map(k => this.normalizeWriteValue(k, data[k]));
122
122
  const { sql: whereClause, params: whereValues } = this.buildWhereClause(where);
123
- const query = `
124
- UPDATE \`${this.model.name}\`
125
- SET ${setClause}
126
- ${whereClause}
123
+ const query = `
124
+ UPDATE \`${this.model.name}\`
125
+ SET ${setClause}
126
+ ${whereClause}
127
127
  `;
128
128
  await getPool().execute(query, [...setValues, ...whereValues]);
129
129
  return this.findOne(where);
@@ -138,10 +138,10 @@ export class Repository {
138
138
  const setClause = setKeys.map(k => `\`${k}\` = ?`).join(", ");
139
139
  const setValues = setKeys.map(k => this.normalizeWriteValue(k, data[k]));
140
140
  const { sql, params } = this.buildWhereClause(where);
141
- const query = `
142
- UPDATE \`${this.model.name}\`
143
- SET ${setClause}
144
- ${sql}
141
+ const query = `
142
+ UPDATE \`${this.model.name}\`
143
+ SET ${setClause}
144
+ ${sql}
145
145
  `;
146
146
  const [res] = await getPool().execute(query, [
147
147
  ...setValues,
@@ -180,11 +180,11 @@ export class Repository {
180
180
  .map(k => `\`${k}\` = VALUES(\`${k}\`)`)
181
181
  .join(",");
182
182
  const values = keys.map(k => this.normalizeWriteValue(k, data[k]));
183
- const sql = `
184
- INSERT INTO \`${this.model.name}\`
185
- (${insertCols})
186
- VALUES (${insertVals})
187
- ON DUPLICATE KEY UPDATE ${updateCols}
183
+ const sql = `
184
+ INSERT INTO \`${this.model.name}\`
185
+ (${insertCols})
186
+ VALUES (${insertVals})
187
+ ON DUPLICATE KEY UPDATE ${updateCols}
188
188
  `;
189
189
  await getPool().execute(sql, values);
190
190
  return (await this.findOne({ [pk]: data[pk] }));
@@ -2,18 +2,18 @@ import { getPool } from "../core/Database.js";
2
2
  export async function getNextId(prefix) {
3
3
  const pool = getPool();
4
4
  // Ensure table exists (safe to call multiple times)
5
- await pool.execute(`
6
- CREATE TABLE IF NOT EXISTS _id_counters (
7
- prefix VARCHAR(255) PRIMARY KEY,
8
- count INT NOT NULL
9
- )
5
+ await pool.execute(`
6
+ CREATE TABLE IF NOT EXISTS _id_counters (
7
+ prefix VARCHAR(255) PRIMARY KEY,
8
+ count INT NOT NULL
9
+ )
10
10
  `);
11
11
  // Atomic upsert + increment
12
12
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
- const [result] = await pool.execute(`
14
- INSERT INTO _id_counters (prefix, count)
15
- VALUES (?, 1)
16
- ON DUPLICATE KEY UPDATE count = count + 1
13
+ const [result] = await pool.execute(`
14
+ INSERT INTO _id_counters (prefix, count)
15
+ VALUES (?, 1)
16
+ ON DUPLICATE KEY UPDATE count = count + 1
17
17
  `, [prefix]);
18
18
  // Fetch the new value
19
19
  const [rows] = await pool.query(`SELECT count FROM _id_counters WHERE prefix = ?`, [prefix]);
@@ -6,14 +6,35 @@ import { getAllModels, getPool } from "../core/Database.js";
6
6
  /* Helpers */
7
7
  /* ---------------------------------- */
8
8
  function mapType(type) {
9
- switch (type) {
9
+ switch (type.toLowerCase()) {
10
+ // strings
10
11
  case "string": return "VARCHAR(255)";
11
- case "json": return "JSON";
12
- case "datetime": return "DATETIME";
12
+ case "text": return "TEXT";
13
+ case "mediumtext": return "MEDIUMTEXT";
14
+ case "longtext": return "LONGTEXT";
15
+ // numbers
13
16
  case "int": return "INT";
17
+ case "bigint": return "BIGINT";
14
18
  case "float": return "FLOAT";
19
+ case "double": return "DOUBLE";
20
+ case "decimal": return "DECIMAL(10,2)";
21
+ // boolean
15
22
  case "boolean": return "BOOLEAN";
16
- default: return type;
23
+ // dates
24
+ case "date": return "DATE";
25
+ case "time": return "TIME";
26
+ case "datetime": return "DATETIME";
27
+ case "timestamp": return "TIMESTAMP";
28
+ // json
29
+ case "json": return "JSON";
30
+ // binary
31
+ case "binary": return "BINARY(16)";
32
+ case "varbinary": return "VARBINARY(255)";
33
+ // uuid (convention)
34
+ case "uuid": return "CHAR(36)";
35
+ // passthrough (escape hatch)
36
+ default:
37
+ return type; // allows raw MySQL like "ENUM(...)", "VARCHAR(64)", etc.
17
38
  }
18
39
  }
19
40
  function formatDefault(value) {
@@ -85,10 +106,20 @@ function generateCreateTableSQL(model) {
85
106
  col += ` DEFAULT ${formatDefault(field.default)}`;
86
107
  columns.push(col);
87
108
  }
88
- const fks = model.foreignKeys.map(fk => `FOREIGN KEY (\`${fk.column}\`) REFERENCES \`${fk.references.table}\`(\`${fk.references.column}\`)`);
89
- return `
90
- CREATE TABLE \`${model.name}\` (
91
- ${[...columns, ...fks].join(",\n ")}
109
+ const fks = model.foreignKeys.map(fk => {
110
+ let sql = `FOREIGN KEY (\`${fk.column}\`) ` +
111
+ `REFERENCES \`${fk.references.table}\`(\`${fk.references.column}\`)`;
112
+ if (fk.onDelete) {
113
+ sql += ` ON DELETE ${fk.onDelete}`;
114
+ }
115
+ if (fk.onUpdate) {
116
+ sql += ` ON UPDATE ${fk.onUpdate}`;
117
+ }
118
+ return sql;
119
+ });
120
+ return `
121
+ CREATE TABLE \`${model.name}\` (
122
+ ${[...columns, ...fks].join(",\n ")}
92
123
  );`.trim();
93
124
  }
94
125
  function generateIndexSQL(model) {
@@ -114,19 +145,19 @@ function emitMigration(sql, dir) {
114
145
  .slice(0, 14);
115
146
  const filename = `${id}_auto_sync.ts`;
116
147
  const filePath = path.join(dir, filename);
117
- const content = `
118
- import type { Migration } from "@shadow-dev/orm";
119
-
120
- export const migration: Migration = {
121
- id: "${id}",
122
- name: "auto_sync",
123
-
124
- async up(db) {
148
+ const content = `
149
+ import type { Migration } from "@shadow-dev/orm";
150
+
151
+ export const migration: Migration = {
152
+ id: "${id}",
153
+ name: "auto_sync",
154
+
155
+ async up(db) {
125
156
  ${sql
126
157
  .map(s => ` await db.exec(\`${s.replace(/`/g, "\\`")}\`);`)
127
- .join("\n")}
128
- }
129
- };
158
+ .join("\n")}
159
+ }
160
+ };
130
161
  `.trim();
131
162
  fs.writeFileSync(filePath, content, { encoding: "utf8" });
132
163
  console.log(`📝 Migration generated: ${filePath}`);
package/package.json CHANGED
@@ -1,54 +1,55 @@
1
- {
2
- "name": "@shadow-dev/orm",
3
- "version": "2.0.0",
4
- "description": "Lightweight dynamic MySQL ORM designed for ShadowCore and modular apps.",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "files": ["dist"],
8
- "type": "module",
9
- "exports": {
10
- ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
- }
14
- },
15
- "scripts": {
16
- "prepublishOnly": "npm run build",
17
- "dev": "ts-node src/index.ts",
18
- "build": "tsc",
19
- "lint": "eslint . --ext .ts",
20
- "type-check": "tsc --noEmit",
21
- "setup": "node scripts/setup.js",
22
- "test": "jest"
23
- },
24
- "repository": {
25
- "type": "git",
26
- "url": "https://github.com/Shadows-Development/ShadowORM.git"
27
- },
28
- "author": "Shadow Development LLC",
29
- "license": "LGPL-3.0-or-later",
30
- "keywords": [
31
- "orm",
32
- "mysql",
33
- "typescript",
34
- "lightweight",
35
- "shadowcore",
36
- "shadow-dev"
37
- ],
38
- "devDependencies": {
39
- "@eslint/json": "^0.13.1",
40
- "@types/jest": "^30.0.0",
41
- "@types/node": "^24.1.0",
42
- "@typescript-eslint/eslint-plugin": "^8.38.0",
43
- "@typescript-eslint/parser": "^8.38.0",
44
- "eslint": "^9.32.0",
45
- "jest": "^30.0.5",
46
- "ts-jest": "^29.4.0",
47
- "ts-node": "^10.9.2",
48
- "typescript": "^5.8.3",
49
- "typescript-eslint": "^8.38.0"
50
- },
51
- "dependencies": {
52
- "mysql2": "^3.14.3"
53
- }
54
- }
1
+ {
2
+ "name": "@shadow-dev/orm",
3
+ "version": "2.0.2",
4
+ "description": "Lightweight dynamic MySQL ORM designed for ShadowCore and modular apps.",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": ["dist"],
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "prepublishOnly": "npm run build",
17
+ "dev": "ts-node src/index.ts",
18
+ "build": "tsc",
19
+ "lint": "eslint . --ext .ts",
20
+ "type-check": "tsc --noEmit",
21
+ "setup": "node scripts/setup.js",
22
+ "test": "jest"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/Shadows-Development/ShadowORM.git"
27
+ },
28
+ "author": "Shadow Development LLC",
29
+ "license": "LGPL-3.0-or-later",
30
+ "keywords": [
31
+ "orm",
32
+ "mysql",
33
+ "typescript",
34
+ "lightweight",
35
+ "shadowcore",
36
+ "shadow-dev"
37
+ ],
38
+ "devDependencies": {
39
+ "@eslint/json": "^0.13.1",
40
+ "@types/jest": "^30.0.0",
41
+ "@types/node": "^24.1.0",
42
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
43
+ "@typescript-eslint/parser": "^8.38.0",
44
+ "eslint": "^9.32.0",
45
+ "jest": "^30.0.5",
46
+ "ts-jest": "^29.4.0",
47
+ "ts-node": "^10.9.2",
48
+ "typescript": "^5.8.3",
49
+ "typescript-eslint": "^8.38.0"
50
+ },
51
+ "dependencies": {
52
+ "mysql2": "^3.14.3"
53
+ }
54
+ }
55
+