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/README.md +4763 -0
- package/cli/ilana.js +928 -0
- package/database/DB.js +85 -0
- package/database/connection.js +114 -0
- package/database/schema-builder.js +219 -0
- package/ilana.config.js +61 -0
- package/ilana.png +0 -0
- package/index.js +34 -0
- package/orm/Collection.js +283 -0
- package/orm/CustomCasts.js +75 -0
- package/orm/Factory.js +372 -0
- package/orm/MigrationRunner.js +458 -0
- package/orm/Model.js +607 -0
- package/orm/ModelRegistry.js +26 -0
- package/orm/QueryBuilder.js +680 -0
- package/orm/Relation.js +299 -0
- package/orm/Seeder.js +153 -0
- package/package.json +71 -0
- package/test-role.js +26 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
class Collection extends Array {
|
|
2
|
+
constructor(items = []) {
|
|
3
|
+
super();
|
|
4
|
+
if (items && items.length > 0) {
|
|
5
|
+
this.push(...items);
|
|
6
|
+
}
|
|
7
|
+
Object.setPrototypeOf(this, Collection.prototype);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Static factory methods
|
|
11
|
+
static make(items = []) {
|
|
12
|
+
return new Collection(items);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static times(count, callback) {
|
|
16
|
+
const items = [];
|
|
17
|
+
for (let i = 0; i < count; i++) {
|
|
18
|
+
items.push(callback(i));
|
|
19
|
+
}
|
|
20
|
+
return new Collection(items);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static range(start, end) {
|
|
24
|
+
const items = [];
|
|
25
|
+
for (let i = start; i <= end; i++) {
|
|
26
|
+
items.push(i);
|
|
27
|
+
}
|
|
28
|
+
return new Collection(items);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Collection methods
|
|
32
|
+
filter(callback) {
|
|
33
|
+
return new Collection(super.filter(callback));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
map(callback) {
|
|
37
|
+
return new Collection(super.map(callback));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
first() {
|
|
41
|
+
return this[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
last() {
|
|
45
|
+
return this[this.length - 1];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pluck(key) {
|
|
49
|
+
return new Collection(this.map(item => item[key]));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
unique(key) {
|
|
53
|
+
if (key) {
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
return new Collection(this.filter(item => {
|
|
56
|
+
const value = item[key];
|
|
57
|
+
if (seen.has(value)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
seen.add(value);
|
|
61
|
+
return true;
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
return new Collection([...new Set(this)]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
groupBy(key) {
|
|
68
|
+
const groups = {};
|
|
69
|
+
|
|
70
|
+
for (const item of this) {
|
|
71
|
+
const groupKey = item[key];
|
|
72
|
+
if (!groups[groupKey]) {
|
|
73
|
+
groups[groupKey] = [];
|
|
74
|
+
}
|
|
75
|
+
groups[groupKey].push(item);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [k, v] of Object.entries(groups)) {
|
|
80
|
+
result[k] = new Collection(v);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
sortBy(key) {
|
|
87
|
+
return new Collection([...this].sort((a, b) => {
|
|
88
|
+
const aVal = a[key];
|
|
89
|
+
const bVal = b[key];
|
|
90
|
+
return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
sortByDesc(key) {
|
|
95
|
+
return new Collection([...this].sort((a, b) => {
|
|
96
|
+
const aVal = a[key];
|
|
97
|
+
const bVal = b[key];
|
|
98
|
+
return aVal > bVal ? -1 : aVal < bVal ? 1 : 0;
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
where(key, value) {
|
|
103
|
+
return new Collection(this.filter(item => item[key] === value));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
firstWhere(key, value) {
|
|
107
|
+
return this.find(item => item[key] === value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
sum(key) {
|
|
111
|
+
if (key) {
|
|
112
|
+
return this.reduce((sum, item) => sum + (item[key] || 0), 0);
|
|
113
|
+
}
|
|
114
|
+
return this.reduce((sum, item) => sum + item, 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
avg(key) {
|
|
118
|
+
if (this.length === 0) return 0;
|
|
119
|
+
return this.sum(key) / this.length;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
min(key) {
|
|
123
|
+
if (this.length === 0) return undefined;
|
|
124
|
+
|
|
125
|
+
if (key) {
|
|
126
|
+
return Math.min(...this.map(item => item[key]));
|
|
127
|
+
}
|
|
128
|
+
return Math.min(...this);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
max(key) {
|
|
132
|
+
if (this.length === 0) return undefined;
|
|
133
|
+
|
|
134
|
+
if (key) {
|
|
135
|
+
return Math.max(...this.map(item => item[key]));
|
|
136
|
+
}
|
|
137
|
+
return Math.max(...this);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
chunk(size) {
|
|
141
|
+
const chunks = [];
|
|
142
|
+
for (let i = 0; i < this.length; i += size) {
|
|
143
|
+
chunks.push(new Collection(this.slice(i, i + size)));
|
|
144
|
+
}
|
|
145
|
+
return new Collection(chunks);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
toJSON() {
|
|
151
|
+
return this.map(item => {
|
|
152
|
+
if (item && typeof item.toJSON === 'function') {
|
|
153
|
+
return item.toJSON();
|
|
154
|
+
}
|
|
155
|
+
return item;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Advanced collection methods
|
|
160
|
+
reject(callback) {
|
|
161
|
+
return new Collection(super.filter((item, index) => !callback(item, index)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
partition(callback) {
|
|
165
|
+
const passed = new Collection();
|
|
166
|
+
const failed = new Collection();
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < this.length; i++) {
|
|
169
|
+
if (callback(this[i], i)) {
|
|
170
|
+
passed.push(this[i]);
|
|
171
|
+
} else {
|
|
172
|
+
failed.push(this[i]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return [passed, failed];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
keyBy(key) {
|
|
180
|
+
const result = {};
|
|
181
|
+
for (const item of this) {
|
|
182
|
+
result[item[key]] = item;
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
countBy(key) {
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const item of this) {
|
|
190
|
+
const value = item[key];
|
|
191
|
+
result[value] = (result[value] || 0) + 1;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
flatten() {
|
|
197
|
+
const result = [];
|
|
198
|
+
for (const item of this) {
|
|
199
|
+
if (Array.isArray(item)) {
|
|
200
|
+
result.push(...item);
|
|
201
|
+
} else {
|
|
202
|
+
result.push(item);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return new Collection(result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
take(count) {
|
|
209
|
+
return new Collection(this.slice(0, count));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
skip(count) {
|
|
213
|
+
return new Collection(this.slice(count));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
random(count = 1) {
|
|
217
|
+
const shuffled = [...this].sort(() => 0.5 - Math.random());
|
|
218
|
+
if (count === 1) {
|
|
219
|
+
return shuffled[0];
|
|
220
|
+
}
|
|
221
|
+
return new Collection(shuffled.slice(0, count));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
shuffle() {
|
|
225
|
+
return new Collection([...this].sort(() => 0.5 - Math.random()));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
tap(callback) {
|
|
229
|
+
callback(this);
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
pipe(callback) {
|
|
234
|
+
return callback(this);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
whenEmpty(callback) {
|
|
238
|
+
if (this.isEmpty()) {
|
|
239
|
+
callback(this);
|
|
240
|
+
}
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
whenNotEmpty(callback) {
|
|
245
|
+
if (this.isNotEmpty()) {
|
|
246
|
+
callback(this);
|
|
247
|
+
}
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
unless(condition, callback) {
|
|
252
|
+
if (!condition) {
|
|
253
|
+
callback(this);
|
|
254
|
+
}
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
when(condition, callback) {
|
|
259
|
+
if (condition) {
|
|
260
|
+
callback(this);
|
|
261
|
+
}
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
toArray() {
|
|
266
|
+
return [...this];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
isEmpty() {
|
|
270
|
+
return this.length === 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
isNotEmpty() {
|
|
274
|
+
return this.length > 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Ensure proper iterator implementation
|
|
278
|
+
[Symbol.iterator]() {
|
|
279
|
+
return super[Symbol.iterator]();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = Collection;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
class MoneyCast {
|
|
2
|
+
get(value) {
|
|
3
|
+
return value ? parseFloat(value) / 100 : null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
set(value) {
|
|
7
|
+
return value ? Math.round(value * 100) : null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class EncryptedCast {
|
|
12
|
+
constructor(key = 'default-key') {
|
|
13
|
+
this.key = key;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get(value) {
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
// Simple base64 decode for demo - use proper encryption in production
|
|
19
|
+
try {
|
|
20
|
+
return Buffer.from(value, 'base64').toString('utf8');
|
|
21
|
+
} catch {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set(value) {
|
|
27
|
+
if (!value) return null;
|
|
28
|
+
// Simple base64 encode for demo - use proper encryption in production
|
|
29
|
+
return Buffer.from(value).toString('base64');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class JsonCast {
|
|
34
|
+
get(value) {
|
|
35
|
+
if (!value) return null;
|
|
36
|
+
return typeof value === 'string' ? JSON.parse(value) : value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set(value) {
|
|
40
|
+
if (value === null || value === undefined) return null;
|
|
41
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class ArrayCast {
|
|
46
|
+
get(value) {
|
|
47
|
+
if (!value) return null;
|
|
48
|
+
return Array.isArray(value) ? value : JSON.parse(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
set(value) {
|
|
52
|
+
if (!value) return null;
|
|
53
|
+
return Array.isArray(value) ? JSON.stringify(value) : value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class DateCast {
|
|
58
|
+
get(value) {
|
|
59
|
+
if (!value) return null;
|
|
60
|
+
return new Date(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set(value) {
|
|
64
|
+
if (!value) return null;
|
|
65
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
MoneyCast,
|
|
71
|
+
EncryptedCast,
|
|
72
|
+
JsonCast,
|
|
73
|
+
ArrayCast,
|
|
74
|
+
DateCast
|
|
75
|
+
};
|
package/orm/Factory.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
const { faker } = require('@faker-js/faker');
|
|
2
|
+
const Model = require('./Model');
|
|
3
|
+
|
|
4
|
+
class Factory {
|
|
5
|
+
constructor(model, definition) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
this.definition = definition;
|
|
8
|
+
this.faker = faker;
|
|
9
|
+
this.states = new Map();
|
|
10
|
+
this.afterCreating = [];
|
|
11
|
+
this.afterMaking = [];
|
|
12
|
+
this.beforeCreating = [];
|
|
13
|
+
this.beforeMaking = [];
|
|
14
|
+
this.count = 1;
|
|
15
|
+
this.currentStates = [];
|
|
16
|
+
this.relationships = new Map();
|
|
17
|
+
this.sequence = 0;
|
|
18
|
+
this.sequences = new Map();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
state(name, definition) {
|
|
22
|
+
if (definition) {
|
|
23
|
+
this.states.set(name, definition);
|
|
24
|
+
} else {
|
|
25
|
+
this.currentStates.push(name);
|
|
26
|
+
}
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
afterCreating(callback) {
|
|
31
|
+
this.afterCreating.push(callback);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
afterMaking(callback) {
|
|
36
|
+
this.afterMaking.push(callback);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
beforeCreating(callback) {
|
|
41
|
+
this.beforeCreating.push(callback);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeMaking(callback) {
|
|
46
|
+
this.beforeMaking.push(callback);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
times(count) {
|
|
51
|
+
this.count = count;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
as(state) {
|
|
56
|
+
this.currentStates.push(state);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for(relation, factory) {
|
|
61
|
+
this.relationships.set(relation, factory);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
has(factory, relation) {
|
|
66
|
+
if (relation) {
|
|
67
|
+
this.relationships.set(relation, factory);
|
|
68
|
+
}
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hasAttached(factory, relation) {
|
|
73
|
+
this.relationships.set(relation, factory);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sequence() {
|
|
78
|
+
return ++this.sequence;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Enhanced sequence methods
|
|
82
|
+
sequenceFor(key) {
|
|
83
|
+
if (!this.sequences) this.sequences = new Map();
|
|
84
|
+
const current = this.sequences.get(key) || 0;
|
|
85
|
+
const next = current + 1;
|
|
86
|
+
this.sequences.set(key, next);
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
resetSequence(key) {
|
|
91
|
+
if (key) {
|
|
92
|
+
this.sequences?.set(key, 0);
|
|
93
|
+
} else {
|
|
94
|
+
this.sequences?.clear();
|
|
95
|
+
this.sequence = 0;
|
|
96
|
+
}
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
raw(attributes = {}) {
|
|
101
|
+
if (this.count === 1) {
|
|
102
|
+
return this.makeRaw(attributes);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const results = [];
|
|
106
|
+
for (let i = 0; i < this.count; i++) {
|
|
107
|
+
results.push(this.makeRaw(attributes));
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async make(attributes = {}) {
|
|
113
|
+
if (this.count === 1) {
|
|
114
|
+
return this.makeOne(attributes);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const models = [];
|
|
118
|
+
for (let i = 0; i < this.count; i++) {
|
|
119
|
+
models.push(await this.makeOne(attributes));
|
|
120
|
+
}
|
|
121
|
+
return models;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async create(attributes = {}) {
|
|
125
|
+
if (this.count === 1) {
|
|
126
|
+
return this.createOne(attributes);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const models = [];
|
|
130
|
+
for (let i = 0; i < this.count; i++) {
|
|
131
|
+
models.push(await this.createOne(attributes));
|
|
132
|
+
}
|
|
133
|
+
return models;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
makeRaw(attributes = {}) {
|
|
137
|
+
let modelAttributes;
|
|
138
|
+
|
|
139
|
+
if (typeof this.definition === 'function') {
|
|
140
|
+
modelAttributes = this.definition(faker);
|
|
141
|
+
} else if (this.definition === undefined && typeof this.definition === 'function') {
|
|
142
|
+
modelAttributes = this.definition();
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error('Factory must have a definition function');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const stateName of this.currentStates) {
|
|
148
|
+
const stateDefinition = this.states.get(stateName);
|
|
149
|
+
if (stateDefinition) {
|
|
150
|
+
modelAttributes = { ...modelAttributes, ...stateDefinition(faker) };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
modelAttributes = { ...modelAttributes, ...attributes };
|
|
155
|
+
|
|
156
|
+
for (const callback of this.beforeMaking) {
|
|
157
|
+
modelAttributes = callback(modelAttributes) || modelAttributes;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return modelAttributes;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async makeOne(attributes = {}) {
|
|
164
|
+
const modelAttributes = this.makeRaw(attributes);
|
|
165
|
+
|
|
166
|
+
const model = new this.model();
|
|
167
|
+
model.fill(modelAttributes);
|
|
168
|
+
|
|
169
|
+
// Run afterMaking callbacks
|
|
170
|
+
for (const callback of this.afterMaking) {
|
|
171
|
+
await callback(model);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return model;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async createOne(attributes = {}) {
|
|
178
|
+
const model = await this.makeOne(attributes);
|
|
179
|
+
|
|
180
|
+
// Run beforeCreating callbacks
|
|
181
|
+
for (const callback of this.beforeCreating) {
|
|
182
|
+
await callback(model);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await model.save();
|
|
186
|
+
|
|
187
|
+
// Run afterCreating callbacks
|
|
188
|
+
for (const callback of this.afterCreating) {
|
|
189
|
+
await callback(model);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return model;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Enhanced factory methods
|
|
196
|
+
configure(callback) {
|
|
197
|
+
callback(this);
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
when(condition, callback) {
|
|
202
|
+
if (condition) {
|
|
203
|
+
callback(this);
|
|
204
|
+
}
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
unless(condition, callback) {
|
|
209
|
+
if (!condition) {
|
|
210
|
+
callback(this);
|
|
211
|
+
}
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Advanced relationship creation
|
|
216
|
+
async createWithRelations(attributes = {}, relations = {}) {
|
|
217
|
+
const model = await this.createOne(attributes);
|
|
218
|
+
|
|
219
|
+
for (const [relationName, relationData] of Object.entries(relations)) {
|
|
220
|
+
const relationMethod = model[relationName];
|
|
221
|
+
if (typeof relationMethod === 'function') {
|
|
222
|
+
const relation = relationMethod.call(model);
|
|
223
|
+
if (relation.constructor.name === 'BelongsToMany') {
|
|
224
|
+
if (Array.isArray(relationData)) {
|
|
225
|
+
for (const data of relationData) {
|
|
226
|
+
await relation.attach(data.id || data, data);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return model;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Batch operations
|
|
237
|
+
async createInBatches(batchSize = 100, attributes = {}) {
|
|
238
|
+
const results = [];
|
|
239
|
+
const totalBatches = Math.ceil(this.count / batchSize);
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < totalBatches; i++) {
|
|
242
|
+
const currentBatchSize = Math.min(batchSize, this.count - (i * batchSize));
|
|
243
|
+
const batchFactory = new Factory(this.model, this.definition);
|
|
244
|
+
batchFactory.count = currentBatchSize;
|
|
245
|
+
batchFactory.currentStates = [...this.currentStates];
|
|
246
|
+
|
|
247
|
+
const batch = await batchFactory.create(attributes);
|
|
248
|
+
results.push(...(Array.isArray(batch) ? batch : [batch]));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return results;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Factory registry
|
|
256
|
+
const factories = new Map();
|
|
257
|
+
|
|
258
|
+
function defineFactory(model, definition) {
|
|
259
|
+
const factory = new Factory(model, definition);
|
|
260
|
+
factories.set(model, factory);
|
|
261
|
+
return factory;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function factory(model) {
|
|
265
|
+
const factory = factories.get(model);
|
|
266
|
+
if (!factory) {
|
|
267
|
+
throw new Error(`No factory defined for model: ${model.name}`);
|
|
268
|
+
}
|
|
269
|
+
return factory;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Enhanced factory utilities
|
|
273
|
+
function factoryForModel(model) {
|
|
274
|
+
return factory(model);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function createFactory(model, definition) {
|
|
278
|
+
return defineFactory(model, definition);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Global factory state management
|
|
282
|
+
const globalSequences = new Map();
|
|
283
|
+
|
|
284
|
+
function globalSequence(key) {
|
|
285
|
+
const current = globalSequences.get(key) || 0;
|
|
286
|
+
const next = current + 1;
|
|
287
|
+
globalSequences.set(key, next);
|
|
288
|
+
return next;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function resetGlobalSequence(key) {
|
|
292
|
+
if (key) {
|
|
293
|
+
globalSequences.set(key, 0);
|
|
294
|
+
} else {
|
|
295
|
+
globalSequences.clear();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Factory trait system
|
|
300
|
+
function trait(callback) {
|
|
301
|
+
return { apply: callback };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Performance optimized factory
|
|
305
|
+
class BulkFactory {
|
|
306
|
+
constructor(model, definition) {
|
|
307
|
+
this.model = model;
|
|
308
|
+
this.definition = definition;
|
|
309
|
+
this.batchSize = 1000;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setBatchSize(size) {
|
|
313
|
+
this.batchSize = size;
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async create(count) {
|
|
318
|
+
const results = [];
|
|
319
|
+
const batches = Math.ceil(count / this.batchSize);
|
|
320
|
+
|
|
321
|
+
for (let i = 0; i < batches; i++) {
|
|
322
|
+
const currentBatchSize = Math.min(this.batchSize, count - (i * this.batchSize));
|
|
323
|
+
const batchData = [];
|
|
324
|
+
|
|
325
|
+
for (let j = 0; j < currentBatchSize; j++) {
|
|
326
|
+
batchData.push(this.definition(faker));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const insertedIds = await this.model.query().insert(batchData);
|
|
330
|
+
|
|
331
|
+
for (let k = 0; k < batchData.length; k++) {
|
|
332
|
+
const model = new this.model();
|
|
333
|
+
model.fill({ ...batchData[k], id: insertedIds[k] });
|
|
334
|
+
model.exists = true;
|
|
335
|
+
results.push(model);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function bulkFactory(model, definition) {
|
|
344
|
+
return new BulkFactory(model, definition);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Add factory method to Model prototype
|
|
348
|
+
if (typeof Model !== 'undefined') {
|
|
349
|
+
Model.factory = function() {
|
|
350
|
+
const existingFactory = factories.get(this);
|
|
351
|
+
if (existingFactory) {
|
|
352
|
+
// Return a new instance to avoid state pollution
|
|
353
|
+
const newFactory = new Factory(this, existingFactory.definition);
|
|
354
|
+
newFactory.states = new Map(existingFactory.states);
|
|
355
|
+
return newFactory;
|
|
356
|
+
}
|
|
357
|
+
throw new Error(`No factory defined for model: ${this.name}`);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
Factory,
|
|
363
|
+
defineFactory,
|
|
364
|
+
factory,
|
|
365
|
+
factoryForModel,
|
|
366
|
+
createFactory,
|
|
367
|
+
globalSequence,
|
|
368
|
+
resetGlobalSequence,
|
|
369
|
+
trait,
|
|
370
|
+
BulkFactory,
|
|
371
|
+
bulkFactory
|
|
372
|
+
};
|