ilana-orm 1.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.
package/database/DB.js ADDED
@@ -0,0 +1,85 @@
1
+ const Database = require('./connection');
2
+
3
+ /**
4
+ * Laravel-style DB facade for IlanaORM
5
+ */
6
+ class DB {
7
+ /**
8
+ * Execute a database transaction with automatic retry for deadlocks
9
+ * @param {Function} callback - Transaction callback function
10
+ * @param {number} attempts - Number of retry attempts (default: 1)
11
+ * @param {string} connection - Connection name (optional)
12
+ * @returns {Promise<any>}
13
+ */
14
+ static async transaction(callback, attempts = 1, connection = null) {
15
+ return Database.transaction(callback, attempts, connection);
16
+ }
17
+
18
+ /**
19
+ * Begin a database transaction manually
20
+ * @param {string} connection - Connection name (optional)
21
+ * @returns {Promise<Transaction>}
22
+ */
23
+ static async beginTransaction(connection = null) {
24
+ return Database.beginTransaction(connection);
25
+ }
26
+
27
+ /**
28
+ * Commit a transaction
29
+ * @param {Transaction} trx - Transaction object
30
+ * @returns {Promise<void>}
31
+ */
32
+ static async commit(trx) {
33
+ return Database.commit(trx);
34
+ }
35
+
36
+ /**
37
+ * Rollback a transaction
38
+ * @param {Transaction} trx - Transaction object
39
+ * @returns {Promise<void>}
40
+ */
41
+ static async rollback(trx) {
42
+ return Database.rollback(trx);
43
+ }
44
+
45
+ /**
46
+ * Get a query builder for a table
47
+ * @param {string} table - Table name
48
+ * @param {string} connection - Connection name (optional)
49
+ * @returns {QueryBuilder}
50
+ */
51
+ static table(table, connection = null) {
52
+ return Database.table(table, connection);
53
+ }
54
+
55
+ /**
56
+ * Execute a raw SQL query
57
+ * @param {string} sql - SQL query
58
+ * @param {Array} bindings - Query bindings
59
+ * @param {string} connection - Connection name (optional)
60
+ * @returns {Promise<any>}
61
+ */
62
+ static raw(sql, bindings = [], connection = null) {
63
+ const db = connection ? Database.connection(connection) : Database.getInstance();
64
+ return db.raw(sql, bindings);
65
+ }
66
+
67
+ /**
68
+ * Get a database connection
69
+ * @param {string} name - Connection name (optional)
70
+ * @returns {Connection}
71
+ */
72
+ static connection(name = null) {
73
+ return Database.connection(name);
74
+ }
75
+
76
+ /**
77
+ * Get the default connection name
78
+ * @returns {string}
79
+ */
80
+ static getDefaultConnection() {
81
+ return Database.getDefaultConnection();
82
+ }
83
+ }
84
+
85
+ module.exports = DB;
@@ -0,0 +1,114 @@
1
+ const knex = require('knex');
2
+
3
+ class Database {
4
+ static connections = new Map();
5
+ static _currentTransaction = null;
6
+
7
+ static configure(config) {
8
+ this.config = config;
9
+ this.defaultConnection = config.default;
10
+
11
+ // Initialize all configured connections
12
+ for (const [name, connConfig] of Object.entries(config.connections)) {
13
+ const connection = knex({
14
+ ...connConfig,
15
+ migrations: config.migrations || {
16
+ directory: './migrations',
17
+ tableName: 'migrations'
18
+ },
19
+ seeds: config.seeds || {
20
+ directory: './seeds'
21
+ }
22
+ });
23
+ this.connections.set(name, connection);
24
+ }
25
+
26
+ // Set default instance after all connections are created
27
+ if (this.connections.has(this.defaultConnection)) {
28
+ this.instance = this.connections.get(this.defaultConnection);
29
+ }
30
+
31
+ if (!this.instance) {
32
+ throw new Error(`Default connection '${this.defaultConnection}' not found in connections config.`);
33
+ }
34
+ }
35
+
36
+ static connection(name) {
37
+ if (!name) return this.getInstance();
38
+ const conn = this.connections.get(name);
39
+ if (!conn) {
40
+ throw new Error(`Database connection '${name}' not configured.`);
41
+ }
42
+ return conn;
43
+ }
44
+
45
+ static getDefaultConnection() {
46
+ return this.defaultConnection;
47
+ }
48
+
49
+ static hasConnection(name) {
50
+ return this.connections.has(name);
51
+ }
52
+
53
+ static getInstance() {
54
+ if (!this.instance) {
55
+ throw new Error('Database not configured. Call Database.configure() first.');
56
+ }
57
+ return this.instance;
58
+ }
59
+
60
+ static async transaction(callback, attempts = 1, connection) {
61
+ const db = this.connection(connection);
62
+
63
+ for (let attempt = 1; attempt <= attempts; attempt++) {
64
+ try {
65
+ return await db.transaction(async (trx) => {
66
+ // Set current transaction for models to use
67
+ this._currentTransaction = trx;
68
+ try {
69
+ const result = await callback(trx);
70
+ return result;
71
+ } finally {
72
+ this._currentTransaction = null;
73
+ }
74
+ });
75
+ } catch (error) {
76
+ if (attempt === attempts) throw error;
77
+ // Wait before retry for deadlock scenarios
78
+ await new Promise(resolve => setTimeout(resolve, 100 * attempt));
79
+ }
80
+ }
81
+ }
82
+
83
+ static async beginTransaction(connection) {
84
+ const db = this.connection(connection);
85
+ const trx = await db.transaction();
86
+ return {
87
+ ...trx,
88
+ commit: () => trx.commit(),
89
+ rollback: () => trx.rollback()
90
+ };
91
+ }
92
+
93
+ static commit(trx) {
94
+ return trx.commit();
95
+ }
96
+
97
+ static rollback(trx) {
98
+ return trx.rollback();
99
+ }
100
+
101
+ static getCurrentTransaction() {
102
+ return this._currentTransaction;
103
+ }
104
+
105
+ static table(tableName, connectionName) {
106
+ return this.connection(connectionName)(tableName);
107
+ }
108
+
109
+ static raw(sql, bindings) {
110
+ return this.getInstance().raw(sql, bindings);
111
+ }
112
+ }
113
+
114
+ module.exports = Database;
@@ -0,0 +1,219 @@
1
+ const Database = require('./connection');
2
+
3
+ class SchemaBuilder {
4
+ constructor(connection) {
5
+ this.knex = Database.connection(connection);
6
+ this.currentTable = '';
7
+ }
8
+
9
+ createTable(tableName, callback) {
10
+ return this.knex.schema.createTable(tableName, callback);
11
+ }
12
+
13
+ dropTable(tableName) {
14
+ return this.knex.schema.dropTable(tableName);
15
+ }
16
+
17
+ dropTableIfExists(tableName) {
18
+ return this.knex.schema.dropTableIfExists(tableName);
19
+ }
20
+
21
+ renameTable(from, to) {
22
+ return this.knex.schema.renameTable(from, to);
23
+ }
24
+
25
+ hasTable(tableName) {
26
+ return this.knex.schema.hasTable(tableName);
27
+ }
28
+
29
+ hasColumn(tableName, columnName) {
30
+ return this.knex.schema.hasColumn(tableName, columnName);
31
+ }
32
+
33
+ table(tableName, callback) {
34
+ return this.knex.schema.table(tableName, callback);
35
+ }
36
+
37
+ alterTable(tableName, callback) {
38
+ return this.knex.schema.alterTable(tableName, callback);
39
+ }
40
+
41
+ raw(statement) {
42
+ return this.knex.raw(statement);
43
+ }
44
+
45
+ // PostgreSQL specific
46
+ createSchema(schemaName) {
47
+ return this.knex.schema.createSchema(schemaName);
48
+ }
49
+
50
+ dropSchema(schemaName) {
51
+ return this.knex.schema.dropSchema(schemaName);
52
+ }
53
+
54
+ // Advanced column types
55
+ jsonb(columnName) {
56
+ if (this.knex.client.config.client === 'pg') {
57
+ return this.knex.schema.jsonb ? this.knex.schema.jsonb(columnName) : this.knex.schema.json(columnName);
58
+ }
59
+ return this.knex.schema.json(columnName);
60
+ }
61
+
62
+ geometry(columnName, geometryType) {
63
+ return this.knex.schema.specificType(columnName, geometryType || 'geometry');
64
+ }
65
+
66
+ point(columnName) {
67
+ return this.knex.schema.specificType(columnName, 'point');
68
+ }
69
+
70
+ lineString(columnName) {
71
+ return this.knex.schema.specificType(columnName, 'linestring');
72
+ }
73
+
74
+ polygon(columnName) {
75
+ return this.knex.schema.specificType(columnName, 'polygon');
76
+ }
77
+
78
+ inet(columnName) {
79
+ return this.knex.schema.specificType(columnName, 'inet');
80
+ }
81
+
82
+ macaddr(columnName) {
83
+ return this.knex.schema.specificType(columnName, 'macaddr');
84
+ }
85
+
86
+ specificType(columnName, type) {
87
+ return this.knex.schema.specificType(columnName, type);
88
+ }
89
+
90
+ // Enhanced column modifiers with database-specific implementations
91
+ after(columnName) {
92
+ if (this.knex.client.config.client === 'mysql2') {
93
+ return this.knex.schema.raw(`AFTER ${columnName}`);
94
+ }
95
+ return this;
96
+ }
97
+
98
+ first() {
99
+ if (this.knex.client.config.client === 'mysql2') {
100
+ return this.knex.schema.raw('FIRST');
101
+ }
102
+ return this;
103
+ }
104
+
105
+ checkPositive(column) {
106
+ const client = this.knex.client.config.client;
107
+ if (client === 'pg') {
108
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_positive CHECK (${column} > 0)`);
109
+ } else if (client === 'mysql2') {
110
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_positive CHECK (${column} > 0)`);
111
+ }
112
+ return Promise.resolve();
113
+ }
114
+
115
+ checkRegex(column, pattern) {
116
+ const client = this.knex.client.config.client;
117
+ if (client === 'pg') {
118
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_regex CHECK (${column} ~ '${pattern}')`);
119
+ } else if (client === 'mysql2') {
120
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_regex CHECK (${column} REGEXP '${pattern}')`);
121
+ }
122
+ return Promise.resolve();
123
+ }
124
+
125
+ generatedAs(column, expression) {
126
+ const client = this.knex.client.config.client;
127
+ if (client === 'mysql2') {
128
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD ${column} VARCHAR(255) GENERATED ALWAYS AS (${expression}) STORED`);
129
+ } else if (client === 'pg') {
130
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD ${column} TEXT GENERATED ALWAYS AS (${expression}) STORED`);
131
+ }
132
+ return Promise.resolve();
133
+ }
134
+
135
+ collate(tableName, collation) {
136
+ const client = this.knex.client.config.client;
137
+ if (client === 'mysql2') {
138
+ return this.knex.raw(`ALTER TABLE ${tableName} COLLATE ${collation}`);
139
+ } else if (client === 'pg') {
140
+ return this.knex.raw(`ALTER TABLE ${tableName} ALTER COLUMN name TYPE TEXT COLLATE "${collation}"`);
141
+ }
142
+ return Promise.resolve();
143
+ }
144
+
145
+ // Enhanced PostgreSQL types
146
+ array(columnName, type = 'text') {
147
+ if (this.knex.client.config.client === 'pg') {
148
+ return this.knex.schema.specificType(columnName, `${type}[]`);
149
+ }
150
+ return this.knex.schema.json(columnName);
151
+ }
152
+
153
+ numrange(columnName) {
154
+ return this.knex.schema.specificType(columnName, 'numrange');
155
+ }
156
+
157
+ daterange(columnName) {
158
+ return this.knex.schema.specificType(columnName, 'daterange');
159
+ }
160
+
161
+ tsvector(columnName) {
162
+ return this.knex.schema.specificType(columnName, 'tsvector');
163
+ }
164
+
165
+ // Enhanced MySQL types
166
+ fulltext(columns, indexName) {
167
+ if (this.knex.client.config.client === 'mysql2') {
168
+ const name = indexName || `${columns.join('_')}_fulltext`;
169
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD FULLTEXT INDEX ${name} (${columns.join(', ')})`);
170
+ }
171
+ return Promise.resolve();
172
+ }
173
+
174
+ spatial(column, indexName) {
175
+ if (this.knex.client.config.client === 'mysql2') {
176
+ const name = indexName || `${column}_spatial`;
177
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD SPATIAL INDEX ${name} (${column})`);
178
+ }
179
+ return Promise.resolve();
180
+ }
181
+
182
+ setCurrentTable(tableName) {
183
+ this.currentTable = tableName;
184
+ return this;
185
+ }
186
+
187
+ // Enhanced utility methods
188
+ get client() {
189
+ return this.knex.client;
190
+ }
191
+
192
+ get fn() {
193
+ return this.knex.fn;
194
+ }
195
+
196
+ // Database-specific utilities
197
+ enableExtension(name) {
198
+ if (this.knex.client.config.client === 'pg') {
199
+ return this.knex.raw(`CREATE EXTENSION IF NOT EXISTS "${name}"`);
200
+ }
201
+ return Promise.resolve();
202
+ }
203
+
204
+ createEnum(name, values) {
205
+ if (this.knex.client.config.client === 'pg') {
206
+ return this.knex.raw(`CREATE TYPE ${name} AS ENUM (${values.map(v => `'${v}'`).join(', ')})`);
207
+ }
208
+ return Promise.resolve();
209
+ }
210
+
211
+ dropEnum(name) {
212
+ if (this.knex.client.config.client === 'pg') {
213
+ return this.knex.raw(`DROP TYPE IF EXISTS ${name}`);
214
+ }
215
+ return Promise.resolve();
216
+ }
217
+ }
218
+
219
+ module.exports = SchemaBuilder;
@@ -0,0 +1,61 @@
1
+ const Database = require('ilana/database/connection').default;
2
+
3
+ const config = {
4
+ default: 'sqlite',
5
+
6
+ connections: {
7
+ sqlite: {
8
+ client: 'sqlite3',
9
+ connection: {
10
+ filename: './database.sqlite'
11
+ }
12
+ },
13
+
14
+ // mysql: {
15
+ // client: 'mysql2',
16
+ // connection: {
17
+ // host: 'localhost',
18
+ // port: 3306,
19
+ // user: 'your_username',
20
+ // password: 'your_password',
21
+ // database: 'your_database'
22
+ // }
23
+ // },
24
+
25
+ // postgres: {
26
+ // client: 'pg',
27
+ // connection: {
28
+ // host: 'localhost',
29
+ // port: 5432,
30
+ // user: 'your_username',
31
+ // password: 'your_password',
32
+ // database: 'your_database'
33
+ // }
34
+ // },
35
+
36
+ // mysql_secondary: {
37
+ // client: 'mysql2',
38
+ // connection: {
39
+ // host: 'secondary.mysql.com',
40
+ // port: 3306,
41
+ // user: 'secondary_user',
42
+ // password: 'secondary_password',
43
+ // database: 'secondary_db'
44
+ // }
45
+ // }
46
+ },
47
+
48
+ migrations: {
49
+ directory: './migrations',
50
+ tableName: 'migrations'
51
+ },
52
+
53
+ seeds: {
54
+ directory: './seeds'
55
+ }
56
+ };
57
+
58
+ // Auto-initialize database connections
59
+ Database.configure(config);
60
+
61
+ module.exports = config;
package/ilana.png ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,34 @@
1
+ // Main exports
2
+ const Model = require('./orm/Model');
3
+ const QueryBuilder = require('./orm/QueryBuilder');
4
+ const Collection = require('./orm/Collection');
5
+ const Database = require('./database/connection');
6
+ const DB = require('./database/DB');
7
+ const SchemaBuilder = require('./database/schema-builder');
8
+ const MigrationRunner = require('./orm/MigrationRunner');
9
+ const Seeder = require('./orm/Seeder');
10
+ const Factory = require('./orm/Factory');
11
+ const Relation = require('./orm/Relation');
12
+ const CustomCasts = require('./orm/CustomCasts');
13
+
14
+ module.exports = {
15
+ Model,
16
+ QueryBuilder,
17
+ Collection,
18
+ Database,
19
+ DB,
20
+ SchemaBuilder,
21
+ MigrationRunner,
22
+ Seeder,
23
+ Factory: Factory.Factory,
24
+ defineFactory: Factory.defineFactory,
25
+
26
+ // Relationships
27
+ ...Relation,
28
+
29
+ // Custom Casts
30
+ ...CustomCasts
31
+ };
32
+
33
+ // Default export
34
+ module.exports.default = module.exports.Model;