ilana-orm 1.0.12 → 1.0.13
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.mjs +169 -0
- package/database/schema-builder.mjs +219 -0
- package/orm/Factory.mjs +198 -0
- package/orm/Seeder.mjs +153 -0
- package/package.json +5 -1
package/database/DB.mjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import Database from './connection.mjs';
|
|
2
|
+
|
|
3
|
+
// DB facade - provides static methods for database operations
|
|
4
|
+
class DB {
|
|
5
|
+
static connection(name) {
|
|
6
|
+
return Database.connection(name);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static table(tableName, connection) {
|
|
10
|
+
return Database.table(tableName, connection);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static raw(query, bindings, connection) {
|
|
14
|
+
return Database.raw(query, bindings, connection);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static select(query, bindings, connection) {
|
|
18
|
+
return Database.select(query, bindings, connection);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static insert(table, data, connection) {
|
|
22
|
+
return Database.insert(table, data, connection);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static update(table, data, where, connection) {
|
|
26
|
+
return Database.update(table, data, where, connection);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static delete(table, where, connection) {
|
|
30
|
+
return Database.delete(table, where, connection);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static transaction(callback, connection) {
|
|
34
|
+
return Database.transaction(callback, connection);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static beginTransaction(connection) {
|
|
38
|
+
return Database.beginTransaction(connection);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static commit(trx) {
|
|
42
|
+
return Database.commit(trx);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static rollback(trx) {
|
|
46
|
+
return Database.rollback(trx);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static schema(connection) {
|
|
50
|
+
return Database.schema(connection);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static migrate(connection) {
|
|
54
|
+
return Database.migrate(connection);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static seed(connection) {
|
|
58
|
+
return Database.seed(connection);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static destroy(connection) {
|
|
62
|
+
return Database.destroy(connection);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static destroyAll() {
|
|
66
|
+
return Database.destroyAll();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static configure(config) {
|
|
70
|
+
return Database.configure(config);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static getInstance(connection) {
|
|
74
|
+
return Database.getInstance(connection);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static getConfig() {
|
|
78
|
+
return Database.getConfig();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static setConfig(config) {
|
|
82
|
+
return Database.setConfig(config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Query builder methods
|
|
86
|
+
static query(connection) {
|
|
87
|
+
return Database.getInstance(connection);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static from(table, connection) {
|
|
91
|
+
return Database.table(table, connection);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Aggregation methods
|
|
95
|
+
static count(table, column = '*', connection) {
|
|
96
|
+
return Database.table(table, connection).count(column);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static sum(table, column, connection) {
|
|
100
|
+
return Database.table(table, connection).sum(column);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static avg(table, column, connection) {
|
|
104
|
+
return Database.table(table, connection).avg(column);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static min(table, column, connection) {
|
|
108
|
+
return Database.table(table, connection).min(column);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static max(table, column, connection) {
|
|
112
|
+
return Database.table(table, connection).max(column);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Utility methods
|
|
116
|
+
static listen(event, callback) {
|
|
117
|
+
return Database.listen(event, callback);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static unlisten(event, callback) {
|
|
121
|
+
return Database.unlisten(event, callback);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static enableQueryLog() {
|
|
125
|
+
return Database.enableQueryLog();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static disableQueryLog() {
|
|
129
|
+
return Database.disableQueryLog();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static getQueryLog() {
|
|
133
|
+
return Database.getQueryLog();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static flushQueryLog() {
|
|
137
|
+
return Database.flushQueryLog();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static pretend(callback, connection) {
|
|
141
|
+
return Database.pretend(callback, connection);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static reconnect(connection) {
|
|
145
|
+
return Database.reconnect(connection);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static disconnect(connection) {
|
|
149
|
+
return Database.disconnect(connection);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static getConnectionName() {
|
|
153
|
+
return Database.getConnectionName();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static setConnectionName(name) {
|
|
157
|
+
return Database.setConnectionName(name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static getTablePrefix(connection) {
|
|
161
|
+
return Database.getTablePrefix(connection);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static setTablePrefix(prefix, connection) {
|
|
165
|
+
return Database.setTablePrefix(prefix, connection);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default DB;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import Database from './connection.mjs';
|
|
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
|
+
export default SchemaBuilder;
|
package/orm/Factory.mjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
|
|
3
|
+
class Factory {
|
|
4
|
+
constructor(model, definition) {
|
|
5
|
+
this.model = model;
|
|
6
|
+
this.definition = definition;
|
|
7
|
+
this.states = new Map();
|
|
8
|
+
this.afterCreating = [];
|
|
9
|
+
this.afterMaking = [];
|
|
10
|
+
this.count = 1;
|
|
11
|
+
this.activeStates = [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static define(model, definition) {
|
|
15
|
+
return new Factory(model, definition);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
state(name, attributes) {
|
|
19
|
+
if (typeof attributes === 'function') {
|
|
20
|
+
this.states.set(name, attributes);
|
|
21
|
+
} else {
|
|
22
|
+
this.states.set(name, () => attributes);
|
|
23
|
+
}
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
times(count) {
|
|
28
|
+
this.count = count;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
as(stateName) {
|
|
33
|
+
this.activeStates.push(stateName);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
afterCreating(callback) {
|
|
38
|
+
this.afterCreating.push(callback);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
afterMaking(callback) {
|
|
43
|
+
this.afterMaking.push(callback);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async make(attributes = {}) {
|
|
48
|
+
const instances = [];
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < this.count; i++) {
|
|
51
|
+
const baseAttributes = this.definition(faker);
|
|
52
|
+
|
|
53
|
+
// Apply states
|
|
54
|
+
let stateAttributes = {};
|
|
55
|
+
for (const stateName of this.activeStates) {
|
|
56
|
+
if (this.states.has(stateName)) {
|
|
57
|
+
const stateDefinition = this.states.get(stateName);
|
|
58
|
+
stateAttributes = { ...stateAttributes, ...stateDefinition(faker) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const finalAttributes = { ...baseAttributes, ...stateAttributes, ...attributes };
|
|
63
|
+
const instance = new this.model(finalAttributes);
|
|
64
|
+
|
|
65
|
+
// Run after making callbacks
|
|
66
|
+
for (const callback of this.afterMaking) {
|
|
67
|
+
await callback(instance);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
instances.push(instance);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return this.count === 1 ? instances[0] : instances;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async create(attributes = {}) {
|
|
77
|
+
const instances = await this.make(attributes);
|
|
78
|
+
const instancesToSave = Array.isArray(instances) ? instances : [instances];
|
|
79
|
+
const savedInstances = [];
|
|
80
|
+
|
|
81
|
+
for (const instance of instancesToSave) {
|
|
82
|
+
const saved = await instance.save();
|
|
83
|
+
|
|
84
|
+
// Run after creating callbacks
|
|
85
|
+
for (const callback of this.afterCreating) {
|
|
86
|
+
await callback(saved);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
savedInstances.push(saved);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this.count === 1 ? savedInstances[0] : savedInstances;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async createMany(count, attributes = {}) {
|
|
96
|
+
return this.times(count).create(attributes);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async makeMany(count, attributes = {}) {
|
|
100
|
+
return this.times(count).make(attributes);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Relationship factories
|
|
104
|
+
for(model) {
|
|
105
|
+
return new RelationshipFactory(this, model);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sequence support
|
|
109
|
+
sequence(callback) {
|
|
110
|
+
let counter = 0;
|
|
111
|
+
const originalDefinition = this.definition;
|
|
112
|
+
|
|
113
|
+
this.definition = (faker) => {
|
|
114
|
+
const baseAttributes = originalDefinition(faker);
|
|
115
|
+
const sequenceAttributes = callback(++counter, faker);
|
|
116
|
+
return { ...baseAttributes, ...sequenceAttributes };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Conditional attributes
|
|
123
|
+
when(condition, attributes) {
|
|
124
|
+
const originalDefinition = this.definition;
|
|
125
|
+
|
|
126
|
+
this.definition = (faker) => {
|
|
127
|
+
const baseAttributes = originalDefinition(faker);
|
|
128
|
+
if (condition) {
|
|
129
|
+
const conditionalAttributes = typeof attributes === 'function'
|
|
130
|
+
? attributes(faker)
|
|
131
|
+
: attributes;
|
|
132
|
+
return { ...baseAttributes, ...conditionalAttributes };
|
|
133
|
+
}
|
|
134
|
+
return baseAttributes;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Raw attributes (bypass model instantiation)
|
|
141
|
+
raw(attributes = {}) {
|
|
142
|
+
const baseAttributes = this.definition(faker);
|
|
143
|
+
|
|
144
|
+
// Apply states
|
|
145
|
+
let stateAttributes = {};
|
|
146
|
+
for (const stateName of this.activeStates) {
|
|
147
|
+
if (this.states.has(stateName)) {
|
|
148
|
+
const stateDefinition = this.states.get(stateName);
|
|
149
|
+
stateAttributes = { ...stateAttributes, ...stateDefinition(faker) };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { ...baseAttributes, ...stateAttributes, ...attributes };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class RelationshipFactory {
|
|
158
|
+
constructor(parentFactory, model) {
|
|
159
|
+
this.parentFactory = parentFactory;
|
|
160
|
+
this.model = model;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
create(attributes = {}) {
|
|
164
|
+
// This would create related models
|
|
165
|
+
// Implementation depends on relationship type
|
|
166
|
+
return this.model.factory().create(attributes);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
make(attributes = {}) {
|
|
170
|
+
return this.model.factory().make(attributes);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Global factory registry
|
|
175
|
+
const factories = new Map();
|
|
176
|
+
|
|
177
|
+
export function defineFactory(model, definition) {
|
|
178
|
+
const factory = new Factory(model, definition);
|
|
179
|
+
factories.set(model.name, factory);
|
|
180
|
+
|
|
181
|
+
// Add factory method to model
|
|
182
|
+
model.factory = function(attributes) {
|
|
183
|
+
const factoryInstance = new Factory(model, definition);
|
|
184
|
+
if (attributes) {
|
|
185
|
+
return factoryInstance.make(attributes);
|
|
186
|
+
}
|
|
187
|
+
return factoryInstance;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return factory;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getFactory(modelName) {
|
|
194
|
+
return factories.get(modelName);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { Factory, faker };
|
|
198
|
+
export default Factory;
|
package/orm/Seeder.mjs
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import Database from '../database/connection.mjs';
|
|
2
|
+
|
|
3
|
+
class Seeder {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.db = Database;
|
|
6
|
+
this.batchSize = 1000;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Batch processing utilities
|
|
10
|
+
async createInBatches(factory, count, attributes = {}) {
|
|
11
|
+
const results = [];
|
|
12
|
+
const batches = Math.ceil(count / this.batchSize);
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < batches; i++) {
|
|
15
|
+
const currentBatchSize = Math.min(this.batchSize, count - (i * this.batchSize));
|
|
16
|
+
const batch = await factory.times(currentBatchSize).create(attributes);
|
|
17
|
+
results.push(...(Array.isArray(batch) ? batch : [batch]));
|
|
18
|
+
|
|
19
|
+
if (batches > 1) {
|
|
20
|
+
console.log(`Batch ${i + 1}/${batches} completed (${results.length}/${count})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async disableForeignKeyChecks() {
|
|
28
|
+
const client = this.db.connection(this.connection).client.config.client;
|
|
29
|
+
|
|
30
|
+
if (client === 'mysql2') {
|
|
31
|
+
await this.db.raw('SET FOREIGN_KEY_CHECKS = 0');
|
|
32
|
+
} else if (client === 'pg') {
|
|
33
|
+
await this.db.raw('SET session_replication_role = replica');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async enableForeignKeyChecks() {
|
|
38
|
+
const client = this.db.connection(this.connection).client.config.client;
|
|
39
|
+
|
|
40
|
+
if (client === 'mysql2') {
|
|
41
|
+
await this.db.raw('SET FOREIGN_KEY_CHECKS = 1');
|
|
42
|
+
} else if (client === 'pg') {
|
|
43
|
+
await this.db.raw('SET session_replication_role = DEFAULT');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async call(seeders) {
|
|
48
|
+
// Handle both single seeder and array of seeders
|
|
49
|
+
const seederArray = Array.isArray(seeders) ? seeders : [seeders];
|
|
50
|
+
|
|
51
|
+
for (const SeederClass of seederArray) {
|
|
52
|
+
const seeder = new SeederClass();
|
|
53
|
+
if (this.connection) {
|
|
54
|
+
seeder.connection = this.connection;
|
|
55
|
+
}
|
|
56
|
+
await seeder.run();
|
|
57
|
+
console.log(`Seeded: ${SeederClass.name}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async callWith(seeders, connection) {
|
|
62
|
+
for (const [name, SeederClass] of Object.entries(seeders)) {
|
|
63
|
+
const seeder = new SeederClass();
|
|
64
|
+
if (connection || this.connection) {
|
|
65
|
+
seeder.connection = connection || this.connection;
|
|
66
|
+
}
|
|
67
|
+
await seeder.run();
|
|
68
|
+
console.log(`Seeded: ${name}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async callOnce(SeederClass, identifier) {
|
|
73
|
+
const tableName = 'seeder_log';
|
|
74
|
+
|
|
75
|
+
// Ensure seeder log table exists
|
|
76
|
+
const knex = this.db.getInstance();
|
|
77
|
+
const hasTable = await knex.schema.hasTable(tableName);
|
|
78
|
+
if (!hasTable) {
|
|
79
|
+
await knex.schema.createTable(tableName, (table) => {
|
|
80
|
+
table.increments('id');
|
|
81
|
+
table.string('seeder');
|
|
82
|
+
table.timestamp('executed_at').defaultTo(knex.fn.now());
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if already executed
|
|
87
|
+
const exists = await this.db.table(tableName).where('seeder', identifier).first();
|
|
88
|
+
if (exists) {
|
|
89
|
+
console.log(`Skipped: ${identifier} (already executed)`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Execute seeder
|
|
94
|
+
const seeder = new SeederClass();
|
|
95
|
+
if (this.connection) {
|
|
96
|
+
seeder.connection = this.connection;
|
|
97
|
+
}
|
|
98
|
+
await seeder.run();
|
|
99
|
+
|
|
100
|
+
// Log execution
|
|
101
|
+
await this.db.table(tableName).insert({
|
|
102
|
+
seeder: identifier,
|
|
103
|
+
executed_at: new Date()
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`Seeded: ${identifier}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async progress(total, callback) {
|
|
110
|
+
let completed = 0;
|
|
111
|
+
const updateProgress = (count) => {
|
|
112
|
+
completed += count;
|
|
113
|
+
const percentage = Math.round((completed / total) * 100);
|
|
114
|
+
console.log(`Progress: ${percentage}% (${completed}/${total})`);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
await callback(updateProgress);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async truncate(table) {
|
|
121
|
+
await this.db.table(table, this.connection).truncate();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async truncateInOrder(tables) {
|
|
125
|
+
await this.disableForeignKeyChecks();
|
|
126
|
+
|
|
127
|
+
for (const table of tables) {
|
|
128
|
+
await this.truncate(table);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await this.enableForeignKeyChecks();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async wipeDatabase() {
|
|
135
|
+
const knex = this.db.connection(this.connection);
|
|
136
|
+
let tables = [];
|
|
137
|
+
|
|
138
|
+
if (knex.client.config.client === 'sqlite3') {
|
|
139
|
+
const result = await knex.raw("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
|
|
140
|
+
tables = result.map((row) => row.name);
|
|
141
|
+
} else if (knex.client.config.client === 'mysql2') {
|
|
142
|
+
const result = await knex.raw('SHOW TABLES');
|
|
143
|
+
tables = result[0].map((row) => Object.values(row)[0]);
|
|
144
|
+
} else if (knex.client.config.client === 'pg') {
|
|
145
|
+
const result = await knex.raw("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
|
|
146
|
+
tables = result.rows.map((row) => row.tablename);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await this.truncateInOrder(tables);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default Seeder;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilana-orm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "A fully-featured, Eloquent-style ORM for Node.js with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -36,12 +36,15 @@
|
|
|
36
36
|
"types": "./database/connection.d.ts"
|
|
37
37
|
},
|
|
38
38
|
"./database/schema-builder": {
|
|
39
|
+
"import": "./database/schema-builder.mjs",
|
|
39
40
|
"require": "./database/schema-builder.js"
|
|
40
41
|
},
|
|
41
42
|
"./orm/Seeder": {
|
|
43
|
+
"import": "./orm/Seeder.mjs",
|
|
42
44
|
"require": "./orm/Seeder.js"
|
|
43
45
|
},
|
|
44
46
|
"./orm/Factory": {
|
|
47
|
+
"import": "./orm/Factory.mjs",
|
|
45
48
|
"require": "./orm/Factory.js"
|
|
46
49
|
},
|
|
47
50
|
"./orm/ModelRegistry": {
|
|
@@ -58,6 +61,7 @@
|
|
|
58
61
|
"require": "./orm/MigrationRunner.js"
|
|
59
62
|
},
|
|
60
63
|
"./database/DB": {
|
|
64
|
+
"import": "./database/DB.mjs",
|
|
61
65
|
"require": "./database/DB.js"
|
|
62
66
|
}
|
|
63
67
|
},
|