@shadow-dev/orm 1.0.3 → 2.0.0

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.
@@ -1,6 +1,15 @@
1
1
  import mysql from "mysql2/promise";
2
- import { Model } from "./Model";
3
- export declare function initDatabase(config: mysql.PoolOptions): void;
2
+ import { Model } from "./Model.js";
3
+ export declare function initDatabase(config: mysql.PoolOptions, options?: {
4
+ migrations?: {
5
+ path: string;
6
+ auto?: boolean;
7
+ };
8
+ }): Promise<void>;
4
9
  export declare function getPool(): mysql.Pool;
5
10
  export declare function registerModel<T>(model: Model<T>): void;
6
11
  export declare function getAllModels(): Map<string, Model<any>>;
12
+ export declare function exec(sql: string, params?: any[]): Promise<mysql.QueryResult>;
13
+ export declare function query<T = any>(sql: string, params?: any[]): Promise<T>;
14
+ export declare function transaction<T>(fn: (conn: mysql.PoolConnection) => Promise<T>): Promise<T>;
15
+ export declare function runMigrations(dir: string): Promise<void>;
@@ -1,28 +1,111 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.initDatabase = initDatabase;
7
- exports.getPool = getPool;
8
- exports.registerModel = registerModel;
9
- exports.getAllModels = getAllModels;
10
1
  // Database.ts
11
- const promise_1 = __importDefault(require("mysql2/promise"));
2
+ import mysql from "mysql2/promise";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { pathToFileURL } from "url";
12
6
  let pool;
13
7
  const modelRegistry = new Map();
14
- function initDatabase(config) {
15
- pool = promise_1.default.createPool(config);
8
+ let migrationsPath = null;
9
+ let autoMigrate = false;
10
+ /* ---------------------------------- */
11
+ /* Initialization */
12
+ /* ---------------------------------- */
13
+ export async function initDatabase(config, options) {
14
+ pool = mysql.createPool(config);
15
+ if (options?.migrations) {
16
+ migrationsPath = options.migrations.path;
17
+ autoMigrate = options.migrations.auto ?? false;
18
+ }
19
+ if (autoMigrate && migrationsPath) {
20
+ await runMigrations(migrationsPath);
21
+ }
16
22
  }
17
- function getPool() {
23
+ export function getPool() {
18
24
  if (!pool)
19
25
  throw new Error("Database not initialized");
20
26
  return pool;
21
27
  }
28
+ /* ---------------------------------- */
29
+ /* Models */
30
+ /* ---------------------------------- */
22
31
  // @ts-expect-error wierd generic errors
23
- function registerModel(model) {
32
+ export function registerModel(model) {
24
33
  modelRegistry.set(model.name, model);
25
34
  }
26
- function getAllModels() {
35
+ export function getAllModels() {
27
36
  return modelRegistry;
28
37
  }
38
+ /* ---------------------------------- */
39
+ /* Low-level helpers */
40
+ /* ---------------------------------- */
41
+ export async function exec(sql, params) {
42
+ const [result] = await getPool().execute(sql, params);
43
+ return result;
44
+ }
45
+ export async function query(sql, params) {
46
+ const [rows] = await getPool().query(sql, params);
47
+ return rows;
48
+ }
49
+ export async function transaction(fn) {
50
+ const conn = await getPool().getConnection();
51
+ try {
52
+ await conn.beginTransaction();
53
+ const result = await fn(conn);
54
+ await conn.commit();
55
+ return result;
56
+ }
57
+ catch (err) {
58
+ await conn.rollback();
59
+ throw err;
60
+ }
61
+ finally {
62
+ conn.release();
63
+ }
64
+ }
65
+ /* ---------------------------------- */
66
+ /* Migrations */
67
+ /* ---------------------------------- */
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
+ );
75
+ `);
76
+ }
77
+ async function loadMigrations(dir) {
78
+ if (!fs.existsSync(dir))
79
+ return [];
80
+ const files = fs
81
+ .readdirSync(dir)
82
+ .filter(f => f.endsWith(".js") || f.endsWith(".ts"));
83
+ const migrations = [];
84
+ for (const file of files) {
85
+ const fullPath = path.resolve(dir, file);
86
+ const fileUrl = pathToFileURL(fullPath).href;
87
+ const mod = await import(fileUrl);
88
+ if (!mod.migration) {
89
+ throw new Error(`Migration ${file} does not export 'migration'`);
90
+ }
91
+ migrations.push(mod.migration);
92
+ }
93
+ return migrations.sort((a, b) => a.id.localeCompare(b.id));
94
+ }
95
+ export async function runMigrations(dir) {
96
+ await ensureMigrationTable();
97
+ const applied = await query(`SELECT id FROM shadoworm_migrations`);
98
+ const appliedIds = new Set(applied.map(m => m.id));
99
+ const migrations = await loadMigrations(dir);
100
+ for (const migration of migrations) {
101
+ if (appliedIds.has(migration.id))
102
+ continue;
103
+ await transaction(async (conn) => {
104
+ await migration.up({
105
+ exec: (sql, params) => conn.execute(sql, params),
106
+ query: (sql, params) => conn.query(sql, params).then(([rows]) => rows)
107
+ });
108
+ await conn.execute(`INSERT INTO shadoworm_migrations (id, name) VALUES (?, ?)`, [migration.id, migration.name]);
109
+ });
110
+ }
111
+ }
@@ -0,0 +1,10 @@
1
+ export interface Migration {
2
+ id: string;
3
+ name: string;
4
+ up(db: MigrationContext): Promise<void> | void;
5
+ down?: (db: MigrationContext) => Promise<void> | void;
6
+ }
7
+ export interface MigrationContext {
8
+ exec(sql: string, params?: any[]): Promise<any>;
9
+ query<T = any>(sql: string, params?: any[]): Promise<T>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,22 +3,37 @@ export interface BaseSchema {
3
3
  data?: any;
4
4
  createdAt?: Date;
5
5
  }
6
- export interface ForeignKeyDefinition {
7
- column: string;
8
- reference: string;
9
- }
10
6
  export type SimpleFieldType = "string" | "int" | "float" | "boolean" | "json" | "datetime";
11
7
  export interface FieldOptions {
12
8
  type: SimpleFieldType;
13
9
  pk?: boolean;
10
+ autoIncrement?: boolean;
14
11
  required?: boolean;
12
+ unique?: boolean;
15
13
  default?: any;
16
14
  }
15
+ export interface IndexDefinition {
16
+ name?: string;
17
+ columns: string[];
18
+ unique?: boolean;
19
+ }
17
20
  export type SchemaValue = SimpleFieldType | FieldOptions;
18
- export type FlexibleSchema<T> = Record<keyof T, SchemaValue>;
21
+ export type NormalizedField = FieldOptions;
22
+ export interface ForeignKeyDefinition {
23
+ column: string;
24
+ references: {
25
+ table: string;
26
+ column: string;
27
+ };
28
+ onDelete?: "CASCADE" | "SET NULL" | "RESTRICT";
29
+ onUpdate?: "CASCADE" | "RESTRICT";
30
+ }
19
31
  export declare class Model<T extends Partial<BaseSchema> = BaseSchema> {
20
32
  readonly name: string;
21
- readonly schema: FlexibleSchema<T>;
22
33
  readonly foreignKeys: ForeignKeyDefinition[];
23
- constructor(name: string, schema: FlexibleSchema<T>, foreignKeys?: ForeignKeyDefinition[]);
34
+ readonly indexes: IndexDefinition[];
35
+ readonly normalizedSchema: Record<keyof T, NormalizedField>;
36
+ constructor(name: string, schema: Record<keyof T, SchemaValue>, foreignKeys?: ForeignKeyDefinition[], indexes?: IndexDefinition[]);
37
+ private normalizeSchema;
38
+ getPrimaryKey(): keyof T | undefined;
24
39
  }
@@ -1,11 +1,41 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Model = void 0;
4
- class Model {
5
- constructor(name, schema, foreignKeys = []) {
1
+ /* ---------------------------------- */
2
+ /* Model */
3
+ /* ---------------------------------- */
4
+ export class Model {
5
+ name;
6
+ foreignKeys;
7
+ indexes;
8
+ normalizedSchema;
9
+ constructor(name, schema, foreignKeys = [], indexes = []) {
6
10
  this.name = name;
7
- this.schema = schema;
8
11
  this.foreignKeys = foreignKeys;
12
+ this.indexes = indexes;
13
+ this.normalizedSchema = this.normalizeSchema(schema);
14
+ }
15
+ normalizeSchema(schema) {
16
+ const normalized = {};
17
+ for (const key of Object.keys(schema)) {
18
+ const value = schema[key];
19
+ if (typeof value === "string") {
20
+ normalized[key] = {
21
+ type: value,
22
+ };
23
+ }
24
+ else {
25
+ normalized[key] = {
26
+ required: false,
27
+ // @ts-expect-error wierd generic errors
28
+ ...value,
29
+ };
30
+ }
31
+ }
32
+ return normalized;
33
+ }
34
+ getPrimaryKey() {
35
+ for (const key of Object.keys(this.normalizedSchema)) {
36
+ if (this.normalizedSchema[key].pk)
37
+ return key;
38
+ }
39
+ return undefined;
9
40
  }
10
41
  }
11
- exports.Model = Model;
@@ -1,15 +1,23 @@
1
- import { Model } from "./Model";
1
+ import { Model } from "./Model.js";
2
2
  export declare class Repository<T extends object> {
3
3
  readonly model: Model<T>;
4
4
  constructor(model: Model<T>);
5
5
  create(data: T): Promise<T>;
6
+ createMany(rows: T[]): Promise<T[]>;
7
+ bulkInsert(rows: T[]): Promise<number>;
6
8
  find(where?: Partial<T>): Promise<T[]>;
7
9
  findOne(where: Partial<T>): Promise<T | null>;
8
- update(where: Partial<T>, data: Partial<T>): Promise<T | null>;
9
- delete(where: Partial<T>): Promise<void>;
10
10
  count(where?: Partial<T>): Promise<number>;
11
11
  exists(where: Partial<T>): Promise<boolean>;
12
- private normalizeValue;
12
+ findById(id: any): Promise<T | null>;
13
+ findManyByIds(ids: any[]): Promise<T[]>;
14
+ update(where: Partial<T>, data: Partial<T>): Promise<T | null>;
15
+ updateMany(where: Partial<T>, data: Partial<T>): Promise<number>;
16
+ delete(where: Partial<T>): Promise<void>;
17
+ deleteMany(where: Partial<T>): Promise<number>;
18
+ upsert(data: T): Promise<T>;
19
+ private getInsertableKeys;
20
+ private normalizeWriteValue;
13
21
  private buildWhereClause;
14
22
  private getPrimaryKeyField;
15
23
  }
@@ -1,57 +1,115 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Repository = void 0;
4
1
  // Repository.ts
5
- const Database_1 = require("./Database");
6
- class Repository {
2
+ import { getPool } from "./Database.js";
3
+ export class Repository {
4
+ model;
7
5
  constructor(model) {
8
6
  this.model = model;
9
- // Validate PK at construction
10
7
  const pk = this.getPrimaryKeyField();
11
8
  if (!pk) {
12
9
  throw new Error(`Model "${model.name}" has no primary key defined (pk: true)`);
13
10
  }
14
11
  }
12
+ /* ---------------------------------- */
13
+ /* CREATE */
14
+ /* ---------------------------------- */
15
15
  async create(data) {
16
- const keys = Object.keys(data);
16
+ const keys = this.getInsertableKeys(data);
17
17
  if (keys.length === 0)
18
18
  throw new Error("create(): empty data");
19
- const sql = `INSERT INTO \`${this.model.name}\` (${keys.map(k => `\`${k}\``).join(",")})
20
- VALUES (${keys.map(() => "?").join(",")})`;
21
- const values = keys.map((key) => this.normalizeValue(data[key]));
22
- const [res] = await (0, Database_1.getPool)().execute(sql, values);
19
+ const sql = `
20
+ INSERT INTO \`${this.model.name}\`
21
+ (${keys.map(k => `\`${k}\``).join(",")})
22
+ VALUES (${keys.map(() => "?").join(",")})
23
+ `;
24
+ const values = keys.map(k => this.normalizeWriteValue(k, data[k]));
25
+ const [res] = await getPool().execute(sql, values);
23
26
  const pk = this.getPrimaryKeyField();
24
- // If PK exists in data, refetch using it
25
- if (pk && data[pk] != null) {
26
- const row = await this.findOne({ [pk]: data[pk] });
27
- if (row)
28
- return row;
29
- }
30
- // If PK is auto-increment and insertId is present
31
- if (pk && res.insertId && res.insertId !== 0) {
32
- const row = await this.findOne({ [pk]: res.insertId });
33
- if (row)
34
- return row;
35
- }
36
- // Fallback — return data + insertId if available
37
- if (res.insertId && res.insertId !== 0) {
38
- return { ...data, id: res.insertId };
27
+ if (pk && res.insertId) {
28
+ return (await this.findById(res.insertId));
39
29
  }
40
30
  return data;
41
31
  }
32
+ async createMany(rows) {
33
+ if (rows.length === 0)
34
+ return [];
35
+ const keys = this.getInsertableKeys(rows[0]);
36
+ if (keys.length === 0)
37
+ throw new Error("createMany(): empty rows");
38
+ const placeholders = rows
39
+ .map(() => `(${keys.map(() => "?").join(",")})`)
40
+ .join(",");
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}
46
+ `;
47
+ const [res] = await getPool().execute(sql, values);
48
+ const pk = this.getPrimaryKeyField();
49
+ if (!pk || !res.insertId)
50
+ return rows;
51
+ const ids = rows.map((_, i) => res.insertId + i);
52
+ return this.findManyByIds(ids);
53
+ }
54
+ async bulkInsert(rows) {
55
+ if (rows.length === 0)
56
+ return 0;
57
+ const keys = this.getInsertableKeys(rows[0]);
58
+ const placeholders = rows
59
+ .map(() => `(${keys.map(() => "?").join(",")})`)
60
+ .join(",");
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}
66
+ `;
67
+ const [res] = await getPool().execute(sql, values);
68
+ return res.affectedRows;
69
+ }
70
+ /* ---------------------------------- */
71
+ /* READ */
72
+ /* ---------------------------------- */
42
73
  async find(where = {}) {
43
74
  const { sql, params } = this.buildWhereClause(where);
44
75
  const query = `SELECT * FROM \`${this.model.name}\` ${sql}`;
45
- const [rows] = await (0, Database_1.getPool)().execute(query, params.map(this.normalizeValue));
76
+ const [rows] = await getPool().execute(query, params);
46
77
  return rows;
47
78
  }
48
79
  async findOne(where) {
49
80
  const { sql, params } = this.buildWhereClause(where);
50
81
  const query = `SELECT * FROM \`${this.model.name}\` ${sql} LIMIT 1`;
51
- const [rows] = await (0, Database_1.getPool)().execute(query, params.map(this.normalizeValue));
52
- const results = rows;
53
- return results.length > 0 ? results[0] : null;
82
+ const [rows] = await getPool().execute(query, params);
83
+ return rows[0] ?? null;
84
+ }
85
+ async count(where = {}) {
86
+ const { sql, params } = this.buildWhereClause(where);
87
+ const query = `SELECT COUNT(*) as count FROM \`${this.model.name}\` ${sql}`;
88
+ const [rows] = await getPool().execute(query, params);
89
+ return rows[0]?.count ?? 0;
90
+ }
91
+ async exists(where) {
92
+ return (await this.count(where)) > 0;
93
+ }
94
+ async findById(id) {
95
+ const pk = this.getPrimaryKeyField();
96
+ return this.findOne({ [pk]: id });
97
+ }
98
+ async findManyByIds(ids) {
99
+ if (ids.length === 0)
100
+ return [];
101
+ const pk = this.getPrimaryKeyField();
102
+ const placeholders = ids.map(() => "?").join(",");
103
+ const query = `
104
+ SELECT * FROM \`${this.model.name}\`
105
+ WHERE \`${String(pk)}\` IN (${placeholders})
106
+ `;
107
+ const [rows] = await getPool().execute(query, ids);
108
+ return rows;
54
109
  }
110
+ /* ---------------------------------- */
111
+ /* UPDATE */
112
+ /* ---------------------------------- */
55
113
  async update(where, data) {
56
114
  if (!where || Object.keys(where).length === 0) {
57
115
  throw new Error("update(): missing WHERE");
@@ -60,41 +118,103 @@ class Repository {
60
118
  if (setKeys.length === 0)
61
119
  return this.findOne(where);
62
120
  const setClause = setKeys.map(k => `\`${k}\` = ?`).join(", ");
63
- const setValues = setKeys.map(k => this.normalizeValue(data[k]));
121
+ const setValues = setKeys.map(k => this.normalizeWriteValue(k, data[k]));
64
122
  const { sql: whereClause, params: whereValues } = this.buildWhereClause(where);
65
- const query = `UPDATE \`${this.model.name}\` SET ${setClause} ${whereClause}`;
66
- await (0, Database_1.getPool)().execute(query, [...setValues, ...whereValues.map(this.normalizeValue)]);
123
+ const query = `
124
+ UPDATE \`${this.model.name}\`
125
+ SET ${setClause}
126
+ ${whereClause}
127
+ `;
128
+ await getPool().execute(query, [...setValues, ...whereValues]);
67
129
  return this.findOne(where);
68
130
  }
131
+ async updateMany(where, data) {
132
+ if (!where || Object.keys(where).length === 0) {
133
+ throw new Error("updateMany(): missing WHERE");
134
+ }
135
+ const setKeys = Object.keys(data);
136
+ if (setKeys.length === 0)
137
+ return 0;
138
+ const setClause = setKeys.map(k => `\`${k}\` = ?`).join(", ");
139
+ const setValues = setKeys.map(k => this.normalizeWriteValue(k, data[k]));
140
+ const { sql, params } = this.buildWhereClause(where);
141
+ const query = `
142
+ UPDATE \`${this.model.name}\`
143
+ SET ${setClause}
144
+ ${sql}
145
+ `;
146
+ const [res] = await getPool().execute(query, [
147
+ ...setValues,
148
+ ...params,
149
+ ]);
150
+ return res.affectedRows;
151
+ }
152
+ /* ---------------------------------- */
153
+ /* DELETE */
154
+ /* ---------------------------------- */
69
155
  async delete(where) {
70
156
  const { sql, params } = this.buildWhereClause(where);
71
157
  const query = `DELETE FROM \`${this.model.name}\` ${sql}`;
72
- await (0, Database_1.getPool)().execute(query, params.map(this.normalizeValue));
158
+ await getPool().execute(query, params);
73
159
  }
74
- async count(where = {}) {
160
+ async deleteMany(where) {
75
161
  const { sql, params } = this.buildWhereClause(where);
76
- const query = `SELECT COUNT(*) as count FROM \`${this.model.name}\` ${sql}`;
77
- const [rows] = await (0, Database_1.getPool)().execute(query, params.map(this.normalizeValue));
78
- return rows[0].count || 0;
162
+ const query = `DELETE FROM \`${this.model.name}\` ${sql}`;
163
+ const [res] = await getPool().execute(query, params);
164
+ return res.affectedRows;
79
165
  }
80
- async exists(where) {
81
- const count = await this.count(where);
82
- return count > 0;
166
+ /* ---------------------------------- */
167
+ /* UPSERT */
168
+ /* ---------------------------------- */
169
+ async upsert(data) {
170
+ const pk = this.getPrimaryKeyField();
171
+ const pkField = this.model.normalizedSchema[pk];
172
+ if (pkField?.autoIncrement) {
173
+ throw new Error("upsert() does not support auto-increment primary keys");
174
+ }
175
+ const keys = this.getInsertableKeys(data);
176
+ const insertCols = keys.map(k => `\`${k}\``).join(",");
177
+ const insertVals = keys.map(() => "?").join(",");
178
+ const updateCols = keys
179
+ .filter(k => k !== pk)
180
+ .map(k => `\`${k}\` = VALUES(\`${k}\`)`)
181
+ .join(",");
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}
188
+ `;
189
+ await getPool().execute(sql, values);
190
+ return (await this.findOne({ [pk]: data[pk] }));
83
191
  }
84
- normalizeValue(value) {
85
- if (value instanceof Date) {
192
+ /* ---------------------------------- */
193
+ /* INTERNAL HELPERS */
194
+ /* ---------------------------------- */
195
+ getInsertableKeys(data) {
196
+ return Object.keys(data).filter(key => {
197
+ const field = this.model.normalizedSchema[key];
198
+ return !(field?.pk && field?.autoIncrement);
199
+ });
200
+ }
201
+ normalizeWriteValue(key, value) {
202
+ const field = this.model.normalizedSchema[key];
203
+ if (value == null)
204
+ return null;
205
+ if (field?.type === "datetime" && value instanceof Date) {
86
206
  return value.toISOString().slice(0, 19).replace("T", " ");
87
207
  }
88
- if (typeof value === "object" && value !== null) {
208
+ if (field?.type === "json") {
89
209
  return JSON.stringify(value);
90
210
  }
91
- return value ?? null;
211
+ return value;
92
212
  }
93
213
  buildWhereClause(where) {
94
214
  const keys = Object.keys(where);
95
215
  if (keys.length === 0)
96
216
  return { sql: "", params: [] };
97
- const conditions = keys.map(k => `${k} = ?`).join(" AND ");
217
+ const conditions = keys.map(k => `\`${k}\` = ?`).join(" AND ");
98
218
  const values = keys.map(k => where[k]);
99
219
  return {
100
220
  sql: `WHERE ${conditions}`,
@@ -102,12 +222,6 @@ class Repository {
102
222
  };
103
223
  }
104
224
  getPrimaryKeyField() {
105
- for (const key of Object.keys(this.model.schema)) {
106
- if (this.model.schema[key]?.pk) {
107
- return key;
108
- }
109
- }
110
- return undefined;
225
+ return this.model.getPrimaryKey();
111
226
  }
112
227
  }
113
- exports.Repository = Repository;
@@ -1,3 +1,4 @@
1
- export * from './Model';
2
- export * from './Repository';
3
- export * from './Database';
1
+ export * from './Model.js';
2
+ export * from './Repository.js';
3
+ export * from './Database.js';
4
+ export * from './Migration.js';
@@ -1,19 +1,4 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./Model"), exports);
18
- __exportStar(require("./Repository"), exports);
19
- __exportStar(require("./Database"), exports);
1
+ export * from './Model.js';
2
+ export * from './Repository.js';
3
+ export * from './Database.js';
4
+ export * from './Migration.js';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,8 @@
1
- export * from './core';
2
- export * from './utils';
1
+ export * from "./core/Database.js";
2
+ export * from "./core/Model.js";
3
+ export * from "./core/Repository.js";
4
+ export * from "./core/Migration.js";
5
+ export * from './utils/getNextId.js';
6
+ export * from './utils/syncSchema.js';
7
+ export * from './utils/types.js';
8
+ export * from './utils/genNewUUID.js';
package/dist/index.js CHANGED
@@ -1,18 +1,8 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./core"), exports);
18
- __exportStar(require("./utils"), exports);
1
+ export * from "./core/Database.js";
2
+ export * from "./core/Model.js";
3
+ export * from "./core/Repository.js";
4
+ export * from "./core/Migration.js";
5
+ export * from './utils/getNextId.js';
6
+ export * from './utils/syncSchema.js';
7
+ export * from './utils/types.js';
8
+ export * from './utils/genNewUUID.js';
@@ -0,0 +1 @@
1
+ export declare function genNewUUID(): string;
@@ -0,0 +1,4 @@
1
+ import { randomUUID } from "crypto";
2
+ export function genNewUUID() {
3
+ return randomUUID();
4
+ }
@@ -1,23 +1,22 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNextId = getNextId;
4
- const core_1 = require("../core");
5
- async function getNextId(prefix) {
6
- const pool = (0, core_1.getPool)();
1
+ import { getPool } from "../core/Database.js";
2
+ export async function getNextId(prefix) {
3
+ const pool = getPool();
4
+ // Ensure table exists (safe to call multiple times)
7
5
  await pool.execute(`
8
- CREATE TABLE IF NOT EXISTS _id_counters (
9
- prefix VARCHAR(255) PRIMARY KEY,
10
- count INT NOT NULL
11
- )
12
- `);
6
+ CREATE TABLE IF NOT EXISTS _id_counters (
7
+ prefix VARCHAR(255) PRIMARY KEY,
8
+ count INT NOT NULL
9
+ )
10
+ `);
11
+ // Atomic upsert + increment
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
17
+ `, [prefix]);
18
+ // Fetch the new value
13
19
  const [rows] = await pool.query(`SELECT count FROM _id_counters WHERE prefix = ?`, [prefix]);
14
- let count = 1;
15
- if (rows.length > 0) {
16
- count = rows[0].count + 1;
17
- await pool.execute(`UPDATE _id_counters SET count = ? WHERE prefix = ?`, [count, prefix]);
18
- }
19
- else {
20
- await pool.execute(`INSERT INTO _id_counters (prefix, count) VALUES (?, ?)`, [prefix, count]);
21
- }
20
+ const count = rows[0].count;
22
21
  return `${prefix}-${String(count).padStart(3, "0")}`;
23
22
  }
@@ -1,3 +1,4 @@
1
- export * from './getNextId';
2
- export * from './syncSchema';
3
- export * from './types';
1
+ export * from './getNextId.js';
2
+ export * from './syncSchema.js';
3
+ export * from './types.js';
4
+ export * from './genNewUUID.js';
@@ -1,19 +1,4 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./getNextId"), exports);
18
- __exportStar(require("./syncSchema"), exports);
19
- __exportStar(require("./types"), exports);
1
+ export * from './getNextId.js';
2
+ export * from './syncSchema.js';
3
+ export * from './types.js';
4
+ export * from './genNewUUID.js';
@@ -1 +1,5 @@
1
- export declare function syncSchema(): Promise<void>;
1
+ export declare function syncSchema(options?: {
2
+ generate?: boolean;
3
+ apply?: boolean;
4
+ migrationsPath?: string;
5
+ }): Promise<void>;
@@ -1,51 +1,133 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.syncSchema = syncSchema;
4
- const core_1 = require("../core");
5
- function normalizeField(value) {
6
- if (typeof value === "string")
7
- return { type: value };
8
- return value;
1
+ // syncSchema.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { getAllModels, getPool } from "../core/Database.js";
5
+ /* ---------------------------------- */
6
+ /* Helpers */
7
+ /* ---------------------------------- */
8
+ function mapType(type) {
9
+ switch (type) {
10
+ case "string": return "VARCHAR(255)";
11
+ case "json": return "JSON";
12
+ case "datetime": return "DATETIME";
13
+ case "int": return "INT";
14
+ case "float": return "FLOAT";
15
+ case "boolean": return "BOOLEAN";
16
+ default: return type;
17
+ }
9
18
  }
10
19
  function formatDefault(value) {
11
20
  if (typeof value === "string")
12
21
  return `'${value}'`;
13
22
  if (typeof value === "boolean")
14
23
  return value ? "TRUE" : "FALSE";
15
- if (value === null || value === undefined)
24
+ if (value == null)
16
25
  return "NULL";
17
26
  return value.toString();
18
27
  }
19
- function mapType(type) {
20
- switch (type.toLowerCase()) {
21
- case "string": return "VARCHAR(255)";
22
- case "json": return "JSON";
23
- case "datetime": return "DATETIME";
24
- case "number": return "INT";
25
- case "float": return "FLOAT";
26
- case "boolean": return "BOOLEAN";
27
- default: return type;
28
- }
28
+ /* ---------------------------------- */
29
+ /* DB Introspection */
30
+ /* ---------------------------------- */
31
+ async function getExistingTables() {
32
+ const pool = getPool();
33
+ const [rows] = await pool.query("SHOW TABLES");
34
+ // @ts-expect-error wierd generic errors
35
+ return new Set(Object.values(rows[0] ?? {}).length ? rows.map(r => Object.values(r)[0]) : []);
29
36
  }
30
- async function syncSchema() {
31
- const models = (0, core_1.getAllModels)();
32
- const pool = (0, core_1.getPool)();
33
- for (const [name, model] of models.entries()) {
34
- const columns = [];
35
- for (const [key, value] of Object.entries(model.schema)) {
36
- const { type, pk, default: def, required } = normalizeField(value);
37
- let col = `\`${key}\` ${mapType(type)}`;
38
- if (required || pk)
39
- col += " NOT NULL";
40
- if (pk)
41
- col += " PRIMARY KEY";
42
- if (def !== undefined)
43
- col += ` DEFAULT ${formatDefault(def)}`;
44
- columns.push(col);
37
+ /* ---------------------------------- */
38
+ /* Migration Generator */
39
+ /* ---------------------------------- */
40
+ export async function syncSchema(options) {
41
+ const generate = options?.generate ?? true;
42
+ const apply = options?.apply ?? false;
43
+ const migrationsPath = options?.migrationsPath ?? "./migrations";
44
+ if (process.env.NODE_ENV === "production" && apply) {
45
+ throw new Error("syncSchema(): cannot apply schema changes in production");
46
+ }
47
+ const models = getAllModels();
48
+ const pool = getPool();
49
+ const existingTables = await getExistingTables();
50
+ const statements = [];
51
+ for (const [tableName, model] of models.entries()) {
52
+ if (existingTables.has(tableName))
53
+ continue;
54
+ statements.push(generateCreateTableSQL(model));
55
+ statements.push(...generateIndexSQL(model));
56
+ }
57
+ if (statements.length === 0) {
58
+ console.log("✅ Schema already in sync.");
59
+ return;
60
+ }
61
+ if (generate) {
62
+ emitMigration(statements, migrationsPath);
63
+ }
64
+ if (apply) {
65
+ for (const sql of statements) {
66
+ await pool.execute(sql);
45
67
  }
46
- const fks = model.foreignKeys.map(fk => `FOREIGN KEY (\`${fk.column}\`) REFERENCES ${fk.reference}`);
47
- const sql = `CREATE TABLE IF NOT EXISTS \`${name}\` (\n ${[...columns, ...fks].join(',\n ')}\n);`;
48
- await pool.execute(sql);
68
+ console.log("✅ Schema applied (dev mode).");
69
+ }
70
+ }
71
+ /* ---------------------------------- */
72
+ /* SQL Builders */
73
+ /* ---------------------------------- */
74
+ function generateCreateTableSQL(model) {
75
+ const columns = [];
76
+ for (const [key, field] of Object.entries(model.normalizedSchema)) {
77
+ let col = `\`${key}\` ${mapType(field.type)}`;
78
+ if (field.pk)
79
+ col += " PRIMARY KEY";
80
+ if (field.autoIncrement)
81
+ col += " AUTO_INCREMENT";
82
+ if (field.required || field.pk)
83
+ col += " NOT NULL";
84
+ if (field.default !== undefined)
85
+ col += ` DEFAULT ${formatDefault(field.default)}`;
86
+ columns.push(col);
87
+ }
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 ")}
92
+ );`.trim();
93
+ }
94
+ function generateIndexSQL(model) {
95
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
96
+ return model.indexes.map((idx, i) => {
97
+ const name = idx.name ??
98
+ `idx_${model.name}_${idx.columns.join("_")}`;
99
+ const unique = idx.unique ? "UNIQUE " : "";
100
+ const cols = idx.columns.map(c => `\`${c}\``).join(", ");
101
+ return `CREATE ${unique}INDEX \`${name}\` ON \`${model.name}\` (${cols});`;
102
+ });
103
+ }
104
+ /* ---------------------------------- */
105
+ /* Migration Writer */
106
+ /* ---------------------------------- */
107
+ function emitMigration(sql, dir) {
108
+ if (!fs.existsSync(dir)) {
109
+ fs.mkdirSync(dir, { recursive: true });
49
110
  }
50
- console.log("✅ Schema synchronized.");
111
+ const id = new Date()
112
+ .toISOString()
113
+ .replace(/[-:T.Z]/g, "")
114
+ .slice(0, 14);
115
+ const filename = `${id}_auto_sync.ts`;
116
+ 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) {
125
+ ${sql
126
+ .map(s => ` await db.exec(\`${s.replace(/`/g, "\\`")}\`);`)
127
+ .join("\n")}
128
+ }
129
+ };
130
+ `.trim();
131
+ fs.writeFileSync(filePath, content, { encoding: "utf8" });
132
+ console.log(`📝 Migration generated: ${filePath}`);
51
133
  }
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@shadow-dev/orm",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "Lightweight dynamic MySQL ORM designed for ShadowCore and modular apps.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "files": ["dist"],
8
+ "type": "module",
8
9
  "exports": {
9
10
  ".": {
10
11
  "import": "./dist/index.js",
11
- "require": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts"
13
13
  }
14
14
  },