ilana-orm 1.0.15 → 1.0.16
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 +139 -108
- package/database/schema-builder.js +18 -12
- package/index.js +1 -0
- package/index.mjs +1 -0
- package/orm/Factory.js +20 -18
- package/orm/Model.d.ts +26 -3
- package/orm/Model.js +138 -25
- package/orm/QueryBuilder.d.ts +24 -2
- package/orm/QueryBuilder.js +437 -75
- package/orm/Relation.d.ts +15 -3
- package/orm/Relation.js +74 -11
- package/orm/Relation.mjs +1 -1
- package/package.json +1 -1
package/orm/Factory.js
CHANGED
|
@@ -7,14 +7,14 @@ class Factory {
|
|
|
7
7
|
this.definition = definition;
|
|
8
8
|
this.faker = faker;
|
|
9
9
|
this.states = new Map();
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
10
|
+
this._afterCreatingCallbacks = [];
|
|
11
|
+
this._afterMakingCallbacks = [];
|
|
12
|
+
this._beforeCreatingCallbacks = [];
|
|
13
|
+
this._beforeMakingCallbacks = [];
|
|
14
14
|
this.count = 1;
|
|
15
15
|
this.currentStates = [];
|
|
16
16
|
this.relationships = new Map();
|
|
17
|
-
this.
|
|
17
|
+
this._sequenceCount = 0;
|
|
18
18
|
this.sequences = new Map();
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -28,22 +28,22 @@ class Factory {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
afterCreating(callback) {
|
|
31
|
-
this.
|
|
31
|
+
this._afterCreatingCallbacks.push(callback);
|
|
32
32
|
return this;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
afterMaking(callback) {
|
|
36
|
-
this.
|
|
36
|
+
this._afterMakingCallbacks.push(callback);
|
|
37
37
|
return this;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
beforeCreating(callback) {
|
|
41
|
-
this.
|
|
41
|
+
this._beforeCreatingCallbacks.push(callback);
|
|
42
42
|
return this;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
beforeMaking(callback) {
|
|
46
|
-
this.
|
|
46
|
+
this._beforeMakingCallbacks.push(callback);
|
|
47
47
|
return this;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -75,7 +75,7 @@ class Factory {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
sequence() {
|
|
78
|
-
return ++this.
|
|
78
|
+
return ++this._sequenceCount;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Enhanced sequence methods
|
|
@@ -92,7 +92,7 @@ class Factory {
|
|
|
92
92
|
this.sequences?.set(key, 0);
|
|
93
93
|
} else {
|
|
94
94
|
this.sequences?.clear();
|
|
95
|
-
this.
|
|
95
|
+
this._sequenceCount = 0;
|
|
96
96
|
}
|
|
97
97
|
return this;
|
|
98
98
|
}
|
|
@@ -138,8 +138,6 @@ class Factory {
|
|
|
138
138
|
|
|
139
139
|
if (typeof this.definition === 'function') {
|
|
140
140
|
modelAttributes = this.definition(faker);
|
|
141
|
-
} else if (this.definition === undefined && typeof this.definition === 'function') {
|
|
142
|
-
modelAttributes = this.definition();
|
|
143
141
|
} else {
|
|
144
142
|
throw new Error('Factory must have a definition function');
|
|
145
143
|
}
|
|
@@ -153,7 +151,7 @@ class Factory {
|
|
|
153
151
|
|
|
154
152
|
modelAttributes = { ...modelAttributes, ...attributes };
|
|
155
153
|
|
|
156
|
-
for (const callback of this.
|
|
154
|
+
for (const callback of this._beforeMakingCallbacks) {
|
|
157
155
|
modelAttributes = callback(modelAttributes) || modelAttributes;
|
|
158
156
|
}
|
|
159
157
|
|
|
@@ -167,7 +165,7 @@ class Factory {
|
|
|
167
165
|
model.fill(modelAttributes);
|
|
168
166
|
|
|
169
167
|
// Run afterMaking callbacks
|
|
170
|
-
for (const callback of this.
|
|
168
|
+
for (const callback of this._afterMakingCallbacks) {
|
|
171
169
|
await callback(model);
|
|
172
170
|
}
|
|
173
171
|
|
|
@@ -178,14 +176,14 @@ class Factory {
|
|
|
178
176
|
const model = await this.makeOne(attributes);
|
|
179
177
|
|
|
180
178
|
// Run beforeCreating callbacks
|
|
181
|
-
for (const callback of this.
|
|
179
|
+
for (const callback of this._beforeCreatingCallbacks) {
|
|
182
180
|
await callback(model);
|
|
183
181
|
}
|
|
184
|
-
|
|
182
|
+
|
|
185
183
|
await model.save();
|
|
186
184
|
|
|
187
185
|
// Run afterCreating callbacks
|
|
188
|
-
for (const callback of this.
|
|
186
|
+
for (const callback of this._afterCreatingCallbacks) {
|
|
189
187
|
await callback(model);
|
|
190
188
|
}
|
|
191
189
|
|
|
@@ -243,6 +241,8 @@ class Factory {
|
|
|
243
241
|
const batchFactory = new Factory(this.model, this.definition);
|
|
244
242
|
batchFactory.count = currentBatchSize;
|
|
245
243
|
batchFactory.currentStates = [...this.currentStates];
|
|
244
|
+
batchFactory._afterCreatingCallbacks = [...this._afterCreatingCallbacks];
|
|
245
|
+
batchFactory._beforeCreatingCallbacks = [...this._beforeCreatingCallbacks];
|
|
246
246
|
|
|
247
247
|
const batch = await batchFactory.create(attributes);
|
|
248
248
|
results.push(...(Array.isArray(batch) ? batch : [batch]));
|
|
@@ -352,6 +352,8 @@ if (typeof Model !== 'undefined') {
|
|
|
352
352
|
// Return a new instance to avoid state pollution
|
|
353
353
|
const newFactory = new Factory(this, existingFactory.definition);
|
|
354
354
|
newFactory.states = new Map(existingFactory.states);
|
|
355
|
+
newFactory._afterCreatingCallbacks = [...existingFactory._afterCreatingCallbacks];
|
|
356
|
+
newFactory._beforeCreatingCallbacks = [...existingFactory._beforeCreatingCallbacks];
|
|
355
357
|
return newFactory;
|
|
356
358
|
}
|
|
357
359
|
throw new Error(`No factory defined for model: ${this.name}`);
|
package/orm/Model.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import QueryBuilder from './QueryBuilder';
|
|
2
|
-
import { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphMany } from './Relation';
|
|
2
|
+
import { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphOne, MorphMany } from './Relation';
|
|
3
3
|
|
|
4
4
|
export interface ModelAttributes {
|
|
5
5
|
[key: string]: any;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export interface CastInstance {
|
|
9
|
+
get(value: any): any;
|
|
10
|
+
set(value: any): any;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export interface ModelCasts {
|
|
9
|
-
[key: string]: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'array' | 'object' | 'float';
|
|
14
|
+
[key: string]: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'array' | 'object' | 'float' | CastInstance;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export interface ModelEvents {
|
|
@@ -26,7 +31,7 @@ export interface Observer {
|
|
|
26
31
|
restored?(model: any): Promise<void> | void;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
export default class Model {
|
|
34
|
+
export default class Model<TAttributes extends ModelAttributes = ModelAttributes> {
|
|
30
35
|
// Static properties
|
|
31
36
|
protected static table: string;
|
|
32
37
|
protected static connection?: string;
|
|
@@ -34,7 +39,10 @@ export default class Model {
|
|
|
34
39
|
protected static keyType: 'number' | 'string';
|
|
35
40
|
protected static incrementing: boolean;
|
|
36
41
|
protected static timestamps: boolean;
|
|
42
|
+
protected static createdAt: string;
|
|
43
|
+
protected static updatedAt: string;
|
|
37
44
|
protected static softDeletes: boolean;
|
|
45
|
+
protected static deletedAt: string;
|
|
38
46
|
protected static fillable: string[];
|
|
39
47
|
protected static guarded: string[];
|
|
40
48
|
protected static casts: ModelCasts;
|
|
@@ -64,6 +72,7 @@ export default class Model {
|
|
|
64
72
|
static resolveRelatedModel(related: string | typeof Model): typeof Model;
|
|
65
73
|
static query(): QueryBuilder;
|
|
66
74
|
static with(...relations: string[]): QueryBuilder;
|
|
75
|
+
static withCount(...relations: string[]): QueryBuilder;
|
|
67
76
|
static on(connectionOrTrx: string | any): QueryBuilder;
|
|
68
77
|
static all(): Promise<Model[]>;
|
|
69
78
|
static find(id: any): Promise<Model | null>;
|
|
@@ -72,6 +81,10 @@ export default class Model {
|
|
|
72
81
|
static firstOrFail(): Promise<Model>;
|
|
73
82
|
static latest(column?: string): QueryBuilder;
|
|
74
83
|
static oldest(column?: string): QueryBuilder;
|
|
84
|
+
static withTrashed(): QueryBuilder;
|
|
85
|
+
static onlyTrashed(): QueryBuilder;
|
|
86
|
+
static upsert(data: any[], uniqueBy: string[], update?: string[]): Promise<any>;
|
|
87
|
+
static withoutGlobalScopes(): QueryBuilder;
|
|
75
88
|
static make(attributes?: ModelAttributes): Model;
|
|
76
89
|
static create(attributes?: ModelAttributes): Promise<Model>;
|
|
77
90
|
static generateUuid(): string;
|
|
@@ -111,10 +124,19 @@ export default class Model {
|
|
|
111
124
|
// Instance methods
|
|
112
125
|
getKey(): any;
|
|
113
126
|
fill(attributes: ModelAttributes): this;
|
|
127
|
+
load(...relations: string[]): Promise<this>;
|
|
128
|
+
loadMissing(...relations: string[]): Promise<this>;
|
|
129
|
+
getRelation(key: string): any;
|
|
130
|
+
relationLoaded(key: string): boolean;
|
|
131
|
+
makeHidden(keys: string | string[]): this;
|
|
132
|
+
makeVisible(keys: string | string[]): this;
|
|
133
|
+
append(keys: string | string[]): this;
|
|
114
134
|
isFillable(key: string): boolean;
|
|
115
135
|
getAttribute(key: string): any;
|
|
116
136
|
setAttribute(key: string, value: any): this;
|
|
117
137
|
syncOriginal(): void;
|
|
138
|
+
getOriginal(key: string): any;
|
|
139
|
+
getOriginal(): ModelAttributes;
|
|
118
140
|
save(): Promise<boolean>;
|
|
119
141
|
update(attributes?: ModelAttributes): Promise<boolean>;
|
|
120
142
|
isDirty(key?: string): boolean;
|
|
@@ -148,6 +170,7 @@ export default class Model {
|
|
|
148
170
|
secondLocalKey?: string
|
|
149
171
|
): HasManyThrough;
|
|
150
172
|
morphTo(typeColumn?: string, idColumn?: string): MorphTo;
|
|
173
|
+
morphOne(related: string | typeof Model, typeColumn?: string, idColumn?: string): MorphOne;
|
|
151
174
|
morphMany(related: string | typeof Model, typeColumn?: string, idColumn?: string): MorphMany;
|
|
152
175
|
|
|
153
176
|
// Protected methods
|
package/orm/Model.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Model.js
|
|
2
2
|
const QueryBuilder = require('./QueryBuilder');
|
|
3
|
-
const { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphMany } = require('./Relation');
|
|
3
|
+
const { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphMany, MorphOne } = require('./Relation');
|
|
4
4
|
const ModelRegistry = require('./ModelRegistry');
|
|
5
5
|
const Database = require('../database/connection');
|
|
6
6
|
|
|
@@ -86,7 +86,8 @@ class Model {
|
|
|
86
86
|
const allKeys = new Set([
|
|
87
87
|
...Object.keys(this.attributes || {}),
|
|
88
88
|
...this.fillable,
|
|
89
|
-
...(this._deferred ? Object.keys(this._deferred) : [])
|
|
89
|
+
...(this._deferred ? Object.keys(this._deferred) : []),
|
|
90
|
+
...(this.appends || [])
|
|
90
91
|
]);
|
|
91
92
|
|
|
92
93
|
for (const key of allKeys) {
|
|
@@ -105,6 +106,16 @@ class Model {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
_toPascalCase(key) {
|
|
110
|
+
return key.replace(/(^|_)([a-z])/g, (_, __, c) => c.toUpperCase());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_selfFk() {
|
|
114
|
+
return this.constructor.name
|
|
115
|
+
.replace(/([A-Z])/g, (m, l, i) => i === 0 ? l.toLowerCase() : '_' + l.toLowerCase())
|
|
116
|
+
+ '_id';
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
// --- Static registry & resolving ---
|
|
109
120
|
static register() {
|
|
110
121
|
ModelRegistry.register(this.name, this);
|
|
@@ -150,6 +161,7 @@ class Model {
|
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
static with(...rels) { return this.query().with(...rels); }
|
|
164
|
+
static withCount(...rels) { return this.query().withCount(...rels); }
|
|
153
165
|
static on(connectionOrTrx) {
|
|
154
166
|
if (connectionOrTrx && typeof connectionOrTrx.raw === 'function') {
|
|
155
167
|
// It's a transaction object
|
|
@@ -166,8 +178,14 @@ class Model {
|
|
|
166
178
|
static async findBy(column, value) { return this.query().where(column, value).first(); }
|
|
167
179
|
static async first() { return this.query().first(); }
|
|
168
180
|
static async firstOrFail() { return this.query().firstOrFail(); }
|
|
169
|
-
static latest(col) { return this.query().latest(col || 'created_at'); }
|
|
170
|
-
static oldest(col) { return this.query().oldest(col || 'created_at'); }
|
|
181
|
+
static latest(col) { return this.query().latest(col || this.createdAt || 'created_at'); }
|
|
182
|
+
static oldest(col) { return this.query().oldest(col || this.createdAt || 'created_at'); }
|
|
183
|
+
static withTrashed() { return this.query().withTrashed(); }
|
|
184
|
+
static onlyTrashed() { return this.query().onlyTrashed(); }
|
|
185
|
+
static async upsert(data, uniqueBy, update) { return this.query().upsert(data, uniqueBy, update); }
|
|
186
|
+
static withoutGlobalScopes() {
|
|
187
|
+
return new QueryBuilder(this.getTableName(), this, this.getConnectionName());
|
|
188
|
+
}
|
|
171
189
|
|
|
172
190
|
static make(attrs = {}) {
|
|
173
191
|
const inst = new this(attrs);
|
|
@@ -192,7 +210,16 @@ class Model {
|
|
|
192
210
|
}
|
|
193
211
|
|
|
194
212
|
static async insert(data) { return this.query().insert(data); }
|
|
195
|
-
static async destroy(ids) {
|
|
213
|
+
static async destroy(ids) {
|
|
214
|
+
const idList = Array.isArray(ids) ? ids : [ids];
|
|
215
|
+
if (this.softDeletes) {
|
|
216
|
+
const models = await this.query().whereIn(this.primaryKey, idList).get();
|
|
217
|
+
let count = 0;
|
|
218
|
+
for (const model of models) { await model.delete(); count++; }
|
|
219
|
+
return count;
|
|
220
|
+
}
|
|
221
|
+
return this.query().whereIn(this.primaryKey, idList).delete();
|
|
222
|
+
}
|
|
196
223
|
static async firstOrCreate(a, v) { return (await this.query().where(a).first()) || this.create({ ...a, ...v }); }
|
|
197
224
|
static async firstOrNew(a, v) {
|
|
198
225
|
const existing = await this.query().where(a).first();
|
|
@@ -211,8 +238,13 @@ class Model {
|
|
|
211
238
|
}
|
|
212
239
|
|
|
213
240
|
// scopes
|
|
214
|
-
static addGlobalScope(n, s) {
|
|
215
|
-
|
|
241
|
+
static addGlobalScope(n, s) {
|
|
242
|
+
if (!Object.hasOwn(this, 'globalScopes')) this.globalScopes = new Map();
|
|
243
|
+
this.globalScopes.set(n, s);
|
|
244
|
+
}
|
|
245
|
+
static removeGlobalScope(n) {
|
|
246
|
+
if (Object.hasOwn(this, 'globalScopes')) this.globalScopes.delete(n);
|
|
247
|
+
}
|
|
216
248
|
static withoutGlobalScope(n) {
|
|
217
249
|
const qb = new QueryBuilder(this.getTableName(), this, this.getConnectionName());
|
|
218
250
|
const scopes = new Map(this.globalScopes);
|
|
@@ -220,10 +252,17 @@ class Model {
|
|
|
220
252
|
scopes.forEach(s => s(qb));
|
|
221
253
|
return qb;
|
|
222
254
|
}
|
|
223
|
-
static applyGlobalScopes(qb) {
|
|
255
|
+
static applyGlobalScopes(qb) {
|
|
256
|
+
const scopes = Object.hasOwn(this, 'globalScopes') ? this.globalScopes : new Map();
|
|
257
|
+
scopes.forEach(s => s(qb));
|
|
258
|
+
}
|
|
224
259
|
|
|
225
260
|
// events
|
|
226
|
-
static _addEventHandler(evt, fn) {
|
|
261
|
+
static _addEventHandler(evt, fn) {
|
|
262
|
+
if (!Object.hasOwn(this, 'events')) this.events = {};
|
|
263
|
+
this.events[evt] = this.events[evt] || [];
|
|
264
|
+
this.events[evt].push(fn);
|
|
265
|
+
}
|
|
227
266
|
static creating(fn) { this._addEventHandler('creating', fn); }
|
|
228
267
|
static created(fn) { this._addEventHandler('created', fn); }
|
|
229
268
|
static updating(fn) { this._addEventHandler('updating', fn); }
|
|
@@ -254,7 +293,8 @@ class Model {
|
|
|
254
293
|
}
|
|
255
294
|
// static async fireEvent(evt, mdl) { for (const h of this.events[evt] || []) if (await h(mdl) === false) return false; }
|
|
256
295
|
static async fireEvent(evt, mdl) {
|
|
257
|
-
const
|
|
296
|
+
const ownEvents = Object.hasOwn(this, 'events') ? this.events : {};
|
|
297
|
+
const handlers = ownEvents[evt] || [];
|
|
258
298
|
for (const handler of handlers) {
|
|
259
299
|
if (await handler(mdl) === false) return false;
|
|
260
300
|
}
|
|
@@ -278,6 +318,46 @@ class Model {
|
|
|
278
318
|
return this;
|
|
279
319
|
}
|
|
280
320
|
|
|
321
|
+
async load(...relations) {
|
|
322
|
+
const qb = new QueryBuilder(this.constructor.getTableName(), this.constructor, this.constructor.getConnectionName());
|
|
323
|
+
qb.eagerLoad = relations.flat();
|
|
324
|
+
await qb.loadRelations([this]);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async loadMissing(...relations) {
|
|
329
|
+
const toLoad = relations.flat().filter(r => !(r.split('.')[0] in this.relations));
|
|
330
|
+
if (toLoad.length) await this.load(...toLoad);
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getRelation(key) {
|
|
335
|
+
return this.relations[key];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
relationLoaded(key) {
|
|
339
|
+
return Object.prototype.hasOwnProperty.call(this.relations, key);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
makeHidden(keys) {
|
|
343
|
+
const list = Array.isArray(keys) ? keys : [keys];
|
|
344
|
+
this.hidden = [...(this.hidden || []), ...list];
|
|
345
|
+
return this;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
makeVisible(keys) {
|
|
349
|
+
const list = Array.isArray(keys) ? keys : [keys];
|
|
350
|
+
this.hidden = (this.hidden || []).filter(k => !list.includes(k));
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
append(keys) {
|
|
355
|
+
const list = Array.isArray(keys) ? keys : [keys];
|
|
356
|
+
this.appends = [...(this.appends || []), ...list];
|
|
357
|
+
this._createAttributeGetters();
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
|
|
281
361
|
isFillable(k) {
|
|
282
362
|
if (Array.isArray(this.fillable) && this.fillable.length) return this.fillable.includes(k);
|
|
283
363
|
if (this.guarded.includes('*')) return false;
|
|
@@ -285,23 +365,36 @@ class Model {
|
|
|
285
365
|
}
|
|
286
366
|
|
|
287
367
|
getAttribute(k) {
|
|
368
|
+
const accessor = `get${this._toPascalCase(k)}Attribute`;
|
|
369
|
+
if (typeof this[accessor] === 'function') {
|
|
370
|
+
return this[accessor]();
|
|
371
|
+
}
|
|
288
372
|
const val = this.attributes[k];
|
|
289
373
|
const cast = this.casts[k];
|
|
374
|
+
if (cast && typeof cast === 'object' && typeof cast.get === 'function') {
|
|
375
|
+
return cast.get(val);
|
|
376
|
+
}
|
|
290
377
|
if (cast === 'json' || cast === 'array') {
|
|
291
378
|
try { return JSON.parse(val); } catch { return val; }
|
|
292
379
|
}
|
|
293
380
|
if (cast === 'date' && val != null) return val;
|
|
381
|
+
if (cast === 'boolean') return val == null ? val : Boolean(val);
|
|
382
|
+
if (cast === 'number' || cast === 'float') return val == null ? val : Number(val);
|
|
294
383
|
return val;
|
|
295
384
|
}
|
|
296
385
|
|
|
297
386
|
setAttribute(k, v) {
|
|
387
|
+
const mutator = `set${this._toPascalCase(k)}Attribute`;
|
|
388
|
+
if (typeof this[mutator] === 'function') {
|
|
389
|
+
v = this[mutator](v);
|
|
390
|
+
}
|
|
298
391
|
const cast = this.casts[k];
|
|
299
392
|
let val = v;
|
|
300
|
-
if (cast === '
|
|
393
|
+
if (cast && typeof cast === 'object' && typeof cast.set === 'function') {
|
|
394
|
+
val = cast.set(v);
|
|
395
|
+
} else if (cast === 'json' || cast === 'array') {
|
|
301
396
|
val = typeof v === 'string' ? v : JSON.stringify(v);
|
|
302
|
-
}
|
|
303
|
-
if (cast === 'date' && v instanceof Date) {
|
|
304
|
-
// Format date for database storage (YYYY-MM-DD HH:mm:ss)
|
|
397
|
+
} else if (cast === 'date' && v instanceof Date) {
|
|
305
398
|
const year = v.getFullYear();
|
|
306
399
|
const month = String(v.getMonth() + 1).padStart(2, '0');
|
|
307
400
|
const day = String(v.getDate()).padStart(2, '0');
|
|
@@ -311,7 +404,6 @@ class Model {
|
|
|
311
404
|
val = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
312
405
|
}
|
|
313
406
|
this.attributes[k] = val;
|
|
314
|
-
// Only mark as dirty if not during initialization and model exists
|
|
315
407
|
if (!this._deferred && this.exists) {
|
|
316
408
|
this._dirty.add(k);
|
|
317
409
|
}
|
|
@@ -323,6 +415,10 @@ class Model {
|
|
|
323
415
|
this._dirty.clear();
|
|
324
416
|
}
|
|
325
417
|
|
|
418
|
+
getOriginal(key) {
|
|
419
|
+
return key !== undefined ? this.original[key] : { ...this.original };
|
|
420
|
+
}
|
|
421
|
+
|
|
326
422
|
_getCurrentTimestamp() {
|
|
327
423
|
const now = new Date();
|
|
328
424
|
const config = this._getConfig();
|
|
@@ -379,9 +475,12 @@ class Model {
|
|
|
379
475
|
if (await this.constructor.fireEvent('creating', this) === false) return false;
|
|
380
476
|
if (await this.constructor.fireEvent('saving', this) === false) return false;
|
|
381
477
|
|
|
478
|
+
const createdAtCol = this.constructor.createdAt || 'created_at';
|
|
479
|
+
const updatedAtCol = this.constructor.updatedAt || 'updated_at';
|
|
480
|
+
|
|
382
481
|
if (this.constructor.timestamps) {
|
|
383
482
|
const now = this._getCurrentTimestamp();
|
|
384
|
-
this.setAttribute(
|
|
483
|
+
this.setAttribute(createdAtCol, now).setAttribute(updatedAtCol, now);
|
|
385
484
|
}
|
|
386
485
|
|
|
387
486
|
// Generate UUID if needed
|
|
@@ -403,16 +502,19 @@ class Model {
|
|
|
403
502
|
await this.constructor.fireEvent('saved', this);
|
|
404
503
|
this.syncOriginal();
|
|
405
504
|
} else if (this.isDirty()) {
|
|
505
|
+
const createdAtCol = this.constructor.createdAt || 'created_at';
|
|
506
|
+
const updatedAtCol = this.constructor.updatedAt || 'updated_at';
|
|
507
|
+
|
|
406
508
|
// Updating existing record
|
|
407
509
|
if (await this.constructor.fireEvent('updating', this) === false) return false;
|
|
408
510
|
if (await this.constructor.fireEvent('saving', this) === false) return false;
|
|
409
511
|
|
|
410
512
|
if (this.constructor.timestamps) {
|
|
411
|
-
this.setAttribute(
|
|
513
|
+
this.setAttribute(updatedAtCol, this._getCurrentTimestamp());
|
|
412
514
|
}
|
|
413
515
|
|
|
414
516
|
const updateData = this.getDirty();
|
|
415
|
-
delete updateData
|
|
517
|
+
delete updateData[createdAtCol]; // Never update created_at
|
|
416
518
|
|
|
417
519
|
if (Object.keys(updateData).length > 0) {
|
|
418
520
|
await this.constructor.query().where(this.constructor.primaryKey, this.getKey()).update(updateData);
|
|
@@ -448,8 +550,10 @@ class Model {
|
|
|
448
550
|
|
|
449
551
|
await this.constructor.fireEvent('deleting', this);
|
|
450
552
|
|
|
553
|
+
const deletedAtCol = this.constructor.deletedAt || 'deleted_at';
|
|
554
|
+
|
|
451
555
|
if (this.constructor.softDeletes) {
|
|
452
|
-
this.setAttribute(
|
|
556
|
+
this.setAttribute(deletedAtCol, new Date());
|
|
453
557
|
await this.save();
|
|
454
558
|
} else {
|
|
455
559
|
await this.constructor.query().where(this.constructor.primaryKey, this.getKey()).delete();
|
|
@@ -461,13 +565,14 @@ class Model {
|
|
|
461
565
|
}
|
|
462
566
|
|
|
463
567
|
async restore() {
|
|
464
|
-
|
|
568
|
+
const deletedAtCol = this.constructor.deletedAt || 'deleted_at';
|
|
569
|
+
if (!this.constructor.softDeletes || !this.getAttribute(deletedAtCol)) {
|
|
465
570
|
return false;
|
|
466
571
|
}
|
|
467
572
|
|
|
468
573
|
await this.constructor.fireEvent('restoring', this);
|
|
469
574
|
|
|
470
|
-
this.setAttribute(
|
|
575
|
+
this.setAttribute(deletedAtCol, null);
|
|
471
576
|
await this.save();
|
|
472
577
|
|
|
473
578
|
await this.constructor.fireEvent('restored', this);
|
|
@@ -475,7 +580,8 @@ class Model {
|
|
|
475
580
|
}
|
|
476
581
|
|
|
477
582
|
trashed() {
|
|
478
|
-
|
|
583
|
+
const deletedAtCol = this.constructor.deletedAt || 'deleted_at';
|
|
584
|
+
return this.constructor.softDeletes && this.getAttribute(deletedAtCol) !== null;
|
|
479
585
|
}
|
|
480
586
|
|
|
481
587
|
only(keys) {
|
|
@@ -556,15 +662,18 @@ class Model {
|
|
|
556
662
|
// relations - convert classes to strings to avoid circular dependencies
|
|
557
663
|
hasOne(related, fk, lk) {
|
|
558
664
|
const relatedName = this._resolveRelatedName(related);
|
|
559
|
-
return new HasOne(this, relatedName, fk ||
|
|
665
|
+
return new HasOne(this, relatedName, fk || this._selfFk(), lk || this.constructor.primaryKey);
|
|
560
666
|
}
|
|
561
667
|
hasMany(related, fk, lk) {
|
|
562
668
|
const relatedName = this._resolveRelatedName(related);
|
|
563
|
-
return new HasMany(this, relatedName, fk ||
|
|
669
|
+
return new HasMany(this, relatedName, fk || this._selfFk(), lk || this.constructor.primaryKey);
|
|
564
670
|
}
|
|
565
671
|
belongsTo(related, fk, ok) {
|
|
566
672
|
const relatedName = this._resolveRelatedName(related);
|
|
567
|
-
|
|
673
|
+
const defaultFk = typeof relatedName === 'string'
|
|
674
|
+
? relatedName.replace(/([A-Z])/g, (m, l, i) => i === 0 ? l.toLowerCase() : '_' + l.toLowerCase()) + '_id'
|
|
675
|
+
: undefined;
|
|
676
|
+
return new BelongsTo(this, relatedName, fk || defaultFk, ok || this.constructor.primaryKey);
|
|
568
677
|
}
|
|
569
678
|
belongsToMany(related, pivot, fp, rp, pk, rk) {
|
|
570
679
|
const relatedName = this._resolveRelatedName(related);
|
|
@@ -590,6 +699,10 @@ class Model {
|
|
|
590
699
|
return new HasManyThrough(this, relatedName, throughName, fk, sk, lk, slk);
|
|
591
700
|
}
|
|
592
701
|
morphTo(type, id) { return new MorphTo(this, type, id); }
|
|
702
|
+
morphOne(related, type, id) {
|
|
703
|
+
const relatedName = typeof related === 'function' && related.name ? related.name : related;
|
|
704
|
+
return new MorphOne(this, relatedName, type, id, this.constructor.name);
|
|
705
|
+
}
|
|
593
706
|
morphMany(related, type, id) {
|
|
594
707
|
const relatedName = typeof related === 'function' && related.name ? related.name : related;
|
|
595
708
|
return new MorphMany(this, relatedName, type, id, this.constructor.name);
|
package/orm/QueryBuilder.d.ts
CHANGED
|
@@ -56,27 +56,44 @@ export default class QueryBuilder {
|
|
|
56
56
|
whereDate(column: string, operator: string, value: string): this;
|
|
57
57
|
whereMonth(column: string, month: number): this;
|
|
58
58
|
whereYear(column: string, year: number): this;
|
|
59
|
-
|
|
59
|
+
whereNotBetween(column: string, range: [any, any]): this;
|
|
60
|
+
orWhereNull(column: string): this;
|
|
61
|
+
orWhereNotNull(column: string): this;
|
|
62
|
+
orWhereIn(column: string, values: any[]): this;
|
|
63
|
+
orWhereNotIn(column: string, values: any[]): this;
|
|
64
|
+
orWhereRaw(sql: string, bindings?: any[]): this;
|
|
65
|
+
whereDay(column: string, operatorOrValue: any, value?: any): this;
|
|
66
|
+
whereTime(column: string, operatorOrValue: any, value?: any): this;
|
|
60
67
|
when<T>(condition: T, callback: (query: this, condition: T) => void, otherwise?: (query: this) => void): this;
|
|
68
|
+
unless<T>(condition: T, callback: (query: this) => void, otherwise?: (query: this, condition: T) => void): this;
|
|
61
69
|
|
|
62
70
|
// Joins
|
|
63
71
|
join(table: string, first: string, operator: string, second: string): this;
|
|
64
72
|
leftJoin(table: string, first: string, operator: string, second: string): this;
|
|
65
73
|
rightJoin(table: string, first: string, operator: string, second: string): this;
|
|
74
|
+
innerJoin(table: string, first: string, operator: string, second: string): this;
|
|
75
|
+
crossJoin(table: string): this;
|
|
66
76
|
|
|
67
77
|
// Ordering and limits
|
|
68
78
|
orderBy(column: string, direction?: 'asc' | 'desc'): this;
|
|
79
|
+
orderByRaw(sql: string): this;
|
|
69
80
|
latest(column?: string): this;
|
|
70
81
|
oldest(column?: string): this;
|
|
82
|
+
inRandomOrder(): this;
|
|
71
83
|
limit(count: number): this;
|
|
72
84
|
offset(count: number): this;
|
|
73
85
|
take(count: number): this;
|
|
74
86
|
skip(count: number): this;
|
|
87
|
+
from(table: string): this;
|
|
88
|
+
forPage(page: number, perPage?: number): this;
|
|
75
89
|
|
|
76
90
|
// Grouping
|
|
77
91
|
groupBy(...columns: string[]): this;
|
|
78
92
|
having(column: string, operator: string, value: any): this;
|
|
79
93
|
having(rawSql: string): this;
|
|
94
|
+
havingRaw(sql: string, bindings?: any[]): this;
|
|
95
|
+
whereExists(callback: (query: QueryBuilder) => void): this;
|
|
96
|
+
whereNotExists(callback: (query: QueryBuilder) => void): this;
|
|
80
97
|
|
|
81
98
|
// Locking
|
|
82
99
|
lockForUpdate(): this;
|
|
@@ -85,7 +102,8 @@ export default class QueryBuilder {
|
|
|
85
102
|
noWait(): this;
|
|
86
103
|
|
|
87
104
|
// Selection
|
|
88
|
-
select(...columns:
|
|
105
|
+
select(...columns: any[]): this;
|
|
106
|
+
addSelect(...columns: any[]): this;
|
|
89
107
|
distinct(): this;
|
|
90
108
|
|
|
91
109
|
// Raw queries
|
|
@@ -105,14 +123,18 @@ export default class QueryBuilder {
|
|
|
105
123
|
withConstraints(relations: { [key: string]: (query: QueryBuilder) => void }): this;
|
|
106
124
|
withCount(...relations: string[]): this;
|
|
107
125
|
whereHas(relation: string, callback?: (query: QueryBuilder) => void): this;
|
|
126
|
+
doesntHave(relation: string): this;
|
|
127
|
+
whereDoesntHave(relation: string, callback?: (query: QueryBuilder) => void): this;
|
|
108
128
|
|
|
109
129
|
// Execution methods
|
|
110
130
|
get(): Promise<Collection<Model>>;
|
|
111
131
|
first(): Promise<Model | null>;
|
|
132
|
+
firstOrFail(): Promise<Model>;
|
|
112
133
|
find(id: any): Promise<Model | null>;
|
|
113
134
|
findOrFail(id: any): Promise<Model>;
|
|
114
135
|
pluck(column: string): Promise<any[]>;
|
|
115
136
|
exists(): Promise<boolean>;
|
|
137
|
+
doesntExist(): Promise<boolean>;
|
|
116
138
|
|
|
117
139
|
// Pagination
|
|
118
140
|
paginate(page?: number, perPage?: number): Promise<PaginationResult<Model>>;
|