@bunnykit/orm 0.1.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/LICENSE +21 -0
- package/README.md +904 -0
- package/dist/bin/bunny.d.ts +2 -0
- package/dist/bin/bunny.js +108 -0
- package/dist/src/connection/Connection.d.ts +13 -0
- package/dist/src/connection/Connection.js +49 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +18 -0
- package/dist/src/migration/Migration.d.ts +4 -0
- package/dist/src/migration/Migration.js +2 -0
- package/dist/src/migration/MigrationCreator.d.ts +5 -0
- package/dist/src/migration/MigrationCreator.js +39 -0
- package/dist/src/migration/Migrator.d.ts +21 -0
- package/dist/src/migration/Migrator.js +137 -0
- package/dist/src/model/BelongsToMany.d.ts +27 -0
- package/dist/src/model/BelongsToMany.js +118 -0
- package/dist/src/model/Model.d.ts +166 -0
- package/dist/src/model/Model.js +763 -0
- package/dist/src/model/MorphMap.d.ts +7 -0
- package/dist/src/model/MorphMap.js +12 -0
- package/dist/src/model/MorphRelations.d.ts +81 -0
- package/dist/src/model/MorphRelations.js +296 -0
- package/dist/src/model/Observer.d.ts +19 -0
- package/dist/src/model/Observer.js +21 -0
- package/dist/src/query/Builder.d.ts +106 -0
- package/dist/src/query/Builder.js +466 -0
- package/dist/src/schema/Blueprint.d.ts +66 -0
- package/dist/src/schema/Blueprint.js +200 -0
- package/dist/src/schema/Schema.d.ts +16 -0
- package/dist/src/schema/Schema.js +135 -0
- package/dist/src/schema/grammars/Grammar.d.ts +26 -0
- package/dist/src/schema/grammars/Grammar.js +95 -0
- package/dist/src/schema/grammars/MySqlGrammar.d.ts +18 -0
- package/dist/src/schema/grammars/MySqlGrammar.js +96 -0
- package/dist/src/schema/grammars/PostgresGrammar.d.ts +16 -0
- package/dist/src/schema/grammars/PostgresGrammar.js +88 -0
- package/dist/src/schema/grammars/SQLiteGrammar.d.ts +16 -0
- package/dist/src/schema/grammars/SQLiteGrammar.js +108 -0
- package/dist/src/typegen/TypeGenerator.d.ts +29 -0
- package/dist/src/typegen/TypeGenerator.js +171 -0
- package/dist/src/typegen/TypeMapper.d.ts +4 -0
- package/dist/src/typegen/TypeMapper.js +27 -0
- package/dist/src/types/index.d.ts +53 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +6 -0
- package/package.json +62 -0
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
import { Builder } from "../query/Builder.js";
|
|
2
|
+
import { snakeCase } from "../utils.js";
|
|
3
|
+
import { ObserverRegistry } from "./Observer.js";
|
|
4
|
+
import { MorphTo, MorphOne, MorphMany, MorphToMany } from "./MorphRelations.js";
|
|
5
|
+
import { BelongsToMany } from "./BelongsToMany.js";
|
|
6
|
+
const globalScopes = new WeakMap();
|
|
7
|
+
function getGlobalScopes(model) {
|
|
8
|
+
const scopes = new Map();
|
|
9
|
+
const chain = [];
|
|
10
|
+
let current = model;
|
|
11
|
+
while (current && current.prototype instanceof Model) {
|
|
12
|
+
chain.unshift(current);
|
|
13
|
+
current = Object.getPrototypeOf(current);
|
|
14
|
+
}
|
|
15
|
+
for (const item of chain) {
|
|
16
|
+
const ownScopes = globalScopes.get(item);
|
|
17
|
+
if (ownScopes) {
|
|
18
|
+
for (const [name, scope] of ownScopes)
|
|
19
|
+
scopes.set(name, scope);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return scopes;
|
|
23
|
+
}
|
|
24
|
+
export class Relation {
|
|
25
|
+
builder;
|
|
26
|
+
parent;
|
|
27
|
+
related;
|
|
28
|
+
foreignKey;
|
|
29
|
+
localKey;
|
|
30
|
+
constructor(parent, related, foreignKey, localKey) {
|
|
31
|
+
this.parent = parent;
|
|
32
|
+
this.related = related;
|
|
33
|
+
this.builder = related.query();
|
|
34
|
+
this.foreignKey = foreignKey || this.defaultForeignKey();
|
|
35
|
+
this.localKey = localKey || related.primaryKey;
|
|
36
|
+
}
|
|
37
|
+
defaultForeignKey() {
|
|
38
|
+
return `${snakeCase(this.parent.constructor.name)}_id`;
|
|
39
|
+
}
|
|
40
|
+
getQuery() {
|
|
41
|
+
return this.builder;
|
|
42
|
+
}
|
|
43
|
+
qualifyRelatedColumn(column) {
|
|
44
|
+
return column.includes(".") ? column : `${this.related.getTable()}.${column}`;
|
|
45
|
+
}
|
|
46
|
+
newExistenceQuery(parentQuery, aggregate, callback) {
|
|
47
|
+
const query = this.related.query().select(aggregate);
|
|
48
|
+
query.whereColumn(`${this.related.getTable()}.${this.foreignKey}`, "=", `${parentQuery.tableName}.${this.localKey}`);
|
|
49
|
+
if (callback)
|
|
50
|
+
callback(query);
|
|
51
|
+
return query;
|
|
52
|
+
}
|
|
53
|
+
getRelationExistenceSql(parentQuery, callback) {
|
|
54
|
+
return this.newExistenceQuery(parentQuery, "1", callback).toSql();
|
|
55
|
+
}
|
|
56
|
+
getRelationCountSql(parentQuery, callback) {
|
|
57
|
+
return this.getRelationAggregateSql(parentQuery, "COUNT(*)", callback);
|
|
58
|
+
}
|
|
59
|
+
getRelationAggregateSql(parentQuery, aggregate, callback) {
|
|
60
|
+
return this.newExistenceQuery(parentQuery, aggregate, callback).toSql();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class HasMany extends Relation {
|
|
64
|
+
constructor(parent, related, foreignKey, localKey) {
|
|
65
|
+
super(parent, related, foreignKey, localKey);
|
|
66
|
+
this.localKey = localKey || parent.constructor.primaryKey;
|
|
67
|
+
this.foreignKey = foreignKey || this.defaultForeignKey();
|
|
68
|
+
this.addConstraints();
|
|
69
|
+
}
|
|
70
|
+
addConstraints() {
|
|
71
|
+
const parentValue = this.parent.getAttribute(this.localKey);
|
|
72
|
+
this.builder.where(this.foreignKey, parentValue);
|
|
73
|
+
}
|
|
74
|
+
addEagerConstraints(models) {
|
|
75
|
+
this.builder = this.related.query();
|
|
76
|
+
const keys = models.map((m) => m.getAttribute(this.localKey));
|
|
77
|
+
this.builder.whereIn(this.foreignKey, keys);
|
|
78
|
+
}
|
|
79
|
+
async getEager() {
|
|
80
|
+
return this.builder.get();
|
|
81
|
+
}
|
|
82
|
+
match(models, results, relationName) {
|
|
83
|
+
const dictionary = {};
|
|
84
|
+
for (const result of results) {
|
|
85
|
+
const key = result.$attributes[this.foreignKey];
|
|
86
|
+
if (!dictionary[key])
|
|
87
|
+
dictionary[key] = [];
|
|
88
|
+
dictionary[key].push(result);
|
|
89
|
+
}
|
|
90
|
+
for (const model of models) {
|
|
91
|
+
const key = model.getAttribute(this.localKey);
|
|
92
|
+
model.setRelation(relationName, dictionary[String(key)] || []);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async getResults() {
|
|
96
|
+
return this.builder.get();
|
|
97
|
+
}
|
|
98
|
+
latestOfMany(column = this.related.primaryKey) {
|
|
99
|
+
return this.ofMany(column, "max");
|
|
100
|
+
}
|
|
101
|
+
oldestOfMany(column = this.related.primaryKey) {
|
|
102
|
+
return this.ofMany(column, "min");
|
|
103
|
+
}
|
|
104
|
+
ofMany(column, aggregate = "max") {
|
|
105
|
+
const relation = new HasOne(this.parent, this.related, this.foreignKey, this.localKey);
|
|
106
|
+
relation.getQuery().orderBy(column, aggregate === "max" ? "desc" : "asc");
|
|
107
|
+
return relation;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export class BelongsTo extends Relation {
|
|
111
|
+
constructor(parent, related, foreignKey, ownerKey) {
|
|
112
|
+
super(parent, related, foreignKey, ownerKey);
|
|
113
|
+
this.foreignKey = foreignKey || `${snakeCase(related.name)}_id`;
|
|
114
|
+
this.localKey = ownerKey || related.primaryKey;
|
|
115
|
+
this.addConstraints();
|
|
116
|
+
}
|
|
117
|
+
addConstraints() {
|
|
118
|
+
const childValue = this.parent.getAttribute(this.foreignKey);
|
|
119
|
+
this.builder.where(this.localKey, childValue);
|
|
120
|
+
}
|
|
121
|
+
addEagerConstraints(models) {
|
|
122
|
+
this.builder = this.related.query();
|
|
123
|
+
const keys = models.map((m) => m.getAttribute(this.foreignKey));
|
|
124
|
+
this.builder.whereIn(this.localKey, keys);
|
|
125
|
+
}
|
|
126
|
+
async getEager() {
|
|
127
|
+
return this.builder.get();
|
|
128
|
+
}
|
|
129
|
+
match(models, results, relationName) {
|
|
130
|
+
const dictionary = {};
|
|
131
|
+
for (const result of results) {
|
|
132
|
+
const key = result.$attributes[this.localKey];
|
|
133
|
+
dictionary[String(key)] = result;
|
|
134
|
+
}
|
|
135
|
+
for (const model of models) {
|
|
136
|
+
const key = model.getAttribute(this.foreignKey);
|
|
137
|
+
model.setRelation(relationName, dictionary[String(key)] || null);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async getResults() {
|
|
141
|
+
return this.builder.first();
|
|
142
|
+
}
|
|
143
|
+
associate(model) {
|
|
144
|
+
const value = model instanceof Model ? model.getAttribute(this.localKey) : model;
|
|
145
|
+
this.parent.setAttribute(this.foreignKey, value);
|
|
146
|
+
return this.parent;
|
|
147
|
+
}
|
|
148
|
+
dissociate() {
|
|
149
|
+
this.parent.setAttribute(this.foreignKey, null);
|
|
150
|
+
return this.parent;
|
|
151
|
+
}
|
|
152
|
+
newExistenceQuery(parentQuery, aggregate, callback) {
|
|
153
|
+
const query = this.related.query().select(aggregate);
|
|
154
|
+
query.whereColumn(`${this.related.getTable()}.${this.localKey}`, "=", `${parentQuery.tableName}.${this.foreignKey}`);
|
|
155
|
+
if (callback)
|
|
156
|
+
callback(query);
|
|
157
|
+
return query;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export class HasManyThrough extends Relation {
|
|
161
|
+
through;
|
|
162
|
+
firstKey;
|
|
163
|
+
secondKey;
|
|
164
|
+
secondLocalKey;
|
|
165
|
+
constructor(parent, related, through, firstKey, secondKey, localKey, secondLocalKey) {
|
|
166
|
+
super(parent, related, secondKey, localKey);
|
|
167
|
+
this.through = through;
|
|
168
|
+
this.localKey = localKey || parent.constructor.primaryKey;
|
|
169
|
+
this.firstKey = firstKey || `${snakeCase(parent.constructor.name)}_id`;
|
|
170
|
+
this.secondKey = secondKey || `${snakeCase(through.name)}_id`;
|
|
171
|
+
this.secondLocalKey = secondLocalKey || through.primaryKey;
|
|
172
|
+
this.addConstraints();
|
|
173
|
+
}
|
|
174
|
+
addConstraints() {
|
|
175
|
+
const throughTable = this.through.getTable();
|
|
176
|
+
const relatedTable = this.related.getTable();
|
|
177
|
+
this.builder.select(`${relatedTable}.*`);
|
|
178
|
+
this.builder.join(throughTable, `${throughTable}.${this.secondLocalKey}`, "=", `${relatedTable}.${this.secondKey}`);
|
|
179
|
+
this.builder.where(`${throughTable}.${this.firstKey}`, this.parent.getAttribute(this.localKey));
|
|
180
|
+
}
|
|
181
|
+
addEagerConstraints(models) {
|
|
182
|
+
const throughTable = this.through.getTable();
|
|
183
|
+
const relatedTable = this.related.getTable();
|
|
184
|
+
const keys = models.map((m) => m.getAttribute(this.localKey));
|
|
185
|
+
this.builder = this.related.query();
|
|
186
|
+
this.builder.select(`${relatedTable}.*`, `${throughTable}.${this.firstKey}`);
|
|
187
|
+
this.builder.join(throughTable, `${throughTable}.${this.secondLocalKey}`, "=", `${relatedTable}.${this.secondKey}`);
|
|
188
|
+
this.builder.whereIn(`${throughTable}.${this.firstKey}`, keys);
|
|
189
|
+
}
|
|
190
|
+
async getEager() {
|
|
191
|
+
return this.builder.get();
|
|
192
|
+
}
|
|
193
|
+
match(models, results, relationName) {
|
|
194
|
+
const dictionary = {};
|
|
195
|
+
for (const result of results) {
|
|
196
|
+
const key = result.$attributes[this.firstKey];
|
|
197
|
+
if (!dictionary[key])
|
|
198
|
+
dictionary[key] = [];
|
|
199
|
+
delete result.$attributes[this.firstKey];
|
|
200
|
+
dictionary[key].push(result);
|
|
201
|
+
}
|
|
202
|
+
for (const model of models) {
|
|
203
|
+
const key = model.getAttribute(this.localKey);
|
|
204
|
+
model.setRelation(relationName, dictionary[String(key)] || []);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async getResults() {
|
|
208
|
+
return this.builder.get();
|
|
209
|
+
}
|
|
210
|
+
newExistenceQuery(parentQuery, aggregate, callback) {
|
|
211
|
+
const throughTable = this.through.getTable();
|
|
212
|
+
const relatedTable = this.related.getTable();
|
|
213
|
+
const query = this.related.query().select(aggregate);
|
|
214
|
+
query.join(throughTable, `${throughTable}.${this.secondLocalKey}`, "=", `${relatedTable}.${this.secondKey}`);
|
|
215
|
+
query.whereColumn(`${throughTable}.${this.firstKey}`, "=", `${parentQuery.tableName}.${this.localKey}`);
|
|
216
|
+
if (callback)
|
|
217
|
+
callback(query);
|
|
218
|
+
return query;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export class HasOneThrough extends HasManyThrough {
|
|
222
|
+
async getResults() {
|
|
223
|
+
return this.builder.first();
|
|
224
|
+
}
|
|
225
|
+
match(models, results, relationName) {
|
|
226
|
+
const dictionary = {};
|
|
227
|
+
for (const result of results) {
|
|
228
|
+
const key = result.$attributes[this.firstKey];
|
|
229
|
+
delete result.$attributes[this.firstKey];
|
|
230
|
+
if (!dictionary[key])
|
|
231
|
+
dictionary[key] = result;
|
|
232
|
+
}
|
|
233
|
+
for (const model of models) {
|
|
234
|
+
const key = model.getAttribute(this.localKey);
|
|
235
|
+
model.setRelation(relationName, dictionary[String(key)] || null);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export class HasOne extends Relation {
|
|
240
|
+
constructor(parent, related, foreignKey, localKey) {
|
|
241
|
+
super(parent, related, foreignKey, localKey);
|
|
242
|
+
this.localKey = localKey || parent.constructor.primaryKey;
|
|
243
|
+
this.foreignKey = foreignKey || this.defaultForeignKey();
|
|
244
|
+
this.addConstraints();
|
|
245
|
+
}
|
|
246
|
+
addConstraints() {
|
|
247
|
+
const parentValue = this.parent.getAttribute(this.localKey);
|
|
248
|
+
this.builder.where(this.foreignKey, parentValue);
|
|
249
|
+
}
|
|
250
|
+
addEagerConstraints(models) {
|
|
251
|
+
this.builder = this.related.query();
|
|
252
|
+
const keys = models.map((m) => m.getAttribute(this.localKey));
|
|
253
|
+
this.builder.whereIn(this.foreignKey, keys);
|
|
254
|
+
}
|
|
255
|
+
async getEager() {
|
|
256
|
+
return this.builder.get();
|
|
257
|
+
}
|
|
258
|
+
match(models, results, relationName) {
|
|
259
|
+
const dictionary = {};
|
|
260
|
+
for (const result of results) {
|
|
261
|
+
const key = result.$attributes[this.foreignKey];
|
|
262
|
+
dictionary[String(key)] = result;
|
|
263
|
+
}
|
|
264
|
+
for (const model of models) {
|
|
265
|
+
const key = model.getAttribute(this.localKey);
|
|
266
|
+
model.setRelation(relationName, dictionary[String(key)] || null);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async getResults() {
|
|
270
|
+
return this.builder.first();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
export class Model {
|
|
274
|
+
static table;
|
|
275
|
+
static primaryKey = "id";
|
|
276
|
+
static timestamps = true;
|
|
277
|
+
static connection;
|
|
278
|
+
static dateFormat = "YYYY-MM-DD HH:mm:ss";
|
|
279
|
+
static morphName;
|
|
280
|
+
static casts = {};
|
|
281
|
+
static fillable = [];
|
|
282
|
+
static guarded = [];
|
|
283
|
+
static attributes = {};
|
|
284
|
+
static softDeletes = false;
|
|
285
|
+
static deletedAtColumn = "deleted_at";
|
|
286
|
+
$attributes = {};
|
|
287
|
+
$original = {};
|
|
288
|
+
$exists = false;
|
|
289
|
+
$relations = {};
|
|
290
|
+
$casts = {};
|
|
291
|
+
constructor(attributes) {
|
|
292
|
+
const defaults = this.constructor.attributes;
|
|
293
|
+
if (Object.keys(defaults).length > 0) {
|
|
294
|
+
this.fill({ ...defaults });
|
|
295
|
+
}
|
|
296
|
+
if (attributes) {
|
|
297
|
+
this.fill(attributes);
|
|
298
|
+
}
|
|
299
|
+
return new Proxy(this, {
|
|
300
|
+
get(target, prop, receiver) {
|
|
301
|
+
if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
|
|
302
|
+
return target.getAttribute(prop);
|
|
303
|
+
}
|
|
304
|
+
return Reflect.get(target, prop, receiver);
|
|
305
|
+
},
|
|
306
|
+
set(target, prop, value, receiver) {
|
|
307
|
+
if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
|
|
308
|
+
target.setAttribute(prop, value);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return Reflect.set(target, prop, value, receiver);
|
|
312
|
+
},
|
|
313
|
+
has(target, prop) {
|
|
314
|
+
return prop in target || (typeof prop === "string" && prop in target.$attributes);
|
|
315
|
+
},
|
|
316
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
317
|
+
if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
|
|
318
|
+
return {
|
|
319
|
+
enumerable: true,
|
|
320
|
+
configurable: true,
|
|
321
|
+
writable: true,
|
|
322
|
+
value: target.getAttribute(prop),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
326
|
+
},
|
|
327
|
+
ownKeys(target) {
|
|
328
|
+
return [...new Set([...Reflect.ownKeys(target), ...Object.keys(target.$attributes)])];
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
static getTable() {
|
|
333
|
+
return this.table || snakeCase(this.name) + "s";
|
|
334
|
+
}
|
|
335
|
+
static getConnection() {
|
|
336
|
+
if (!this.connection) {
|
|
337
|
+
throw new Error(`No connection set on model ${this.name}`);
|
|
338
|
+
}
|
|
339
|
+
return this.connection;
|
|
340
|
+
}
|
|
341
|
+
static setConnection(connection) {
|
|
342
|
+
this.connection = connection;
|
|
343
|
+
}
|
|
344
|
+
static query() {
|
|
345
|
+
const builder = new Builder(this.getConnection(), this.getTable());
|
|
346
|
+
builder.setModel(this);
|
|
347
|
+
this.applyGlobalScopes(builder);
|
|
348
|
+
return builder;
|
|
349
|
+
}
|
|
350
|
+
static addGlobalScope(name, scope) {
|
|
351
|
+
const scopes = globalScopes.get(this) || new Map();
|
|
352
|
+
scopes.set(name, scope);
|
|
353
|
+
globalScopes.set(this, scopes);
|
|
354
|
+
}
|
|
355
|
+
static removeGlobalScope(name) {
|
|
356
|
+
globalScopes.get(this)?.delete(name);
|
|
357
|
+
}
|
|
358
|
+
static applyGlobalScopes(builder) {
|
|
359
|
+
if (this.softDeletes) {
|
|
360
|
+
builder.whereNull(this.getQualifiedDeletedAtColumn(), "and", "softDeletes");
|
|
361
|
+
}
|
|
362
|
+
for (const [name, scope] of getGlobalScopes(this)) {
|
|
363
|
+
scope(builder, this);
|
|
364
|
+
for (const where of builder.wheres) {
|
|
365
|
+
if (!where.scope)
|
|
366
|
+
where.scope = name;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
static getQualifiedDeletedAtColumn() {
|
|
371
|
+
return `${this.getTable()}.${this.deletedAtColumn}`;
|
|
372
|
+
}
|
|
373
|
+
static async create(attributes) {
|
|
374
|
+
const instance = new this();
|
|
375
|
+
instance.fill(attributes);
|
|
376
|
+
await instance.save();
|
|
377
|
+
return instance;
|
|
378
|
+
}
|
|
379
|
+
static async find(id) {
|
|
380
|
+
return this.query().find(id, this.primaryKey);
|
|
381
|
+
}
|
|
382
|
+
static async first() {
|
|
383
|
+
return this.query().first();
|
|
384
|
+
}
|
|
385
|
+
static where(column, operator, value) {
|
|
386
|
+
return this.query().where(column, operator, value);
|
|
387
|
+
}
|
|
388
|
+
static whereIn(column, values) {
|
|
389
|
+
return this.query().whereIn(column, values);
|
|
390
|
+
}
|
|
391
|
+
static whereNull(column) {
|
|
392
|
+
return this.query().whereNull(column);
|
|
393
|
+
}
|
|
394
|
+
static whereNotNull(column) {
|
|
395
|
+
return this.query().whereNotNull(column);
|
|
396
|
+
}
|
|
397
|
+
static orWhere(column, operator, value) {
|
|
398
|
+
return this.query().orWhere(column, operator, value);
|
|
399
|
+
}
|
|
400
|
+
static with(...relations) {
|
|
401
|
+
return this.query().with(...relations);
|
|
402
|
+
}
|
|
403
|
+
static withTrashed() {
|
|
404
|
+
return this.query().withTrashed();
|
|
405
|
+
}
|
|
406
|
+
static onlyTrashed() {
|
|
407
|
+
return this.query().onlyTrashed();
|
|
408
|
+
}
|
|
409
|
+
static withoutGlobalScope(scope) {
|
|
410
|
+
return this.query().withoutGlobalScope(scope);
|
|
411
|
+
}
|
|
412
|
+
static withoutGlobalScopes() {
|
|
413
|
+
return this.query().withoutGlobalScopes();
|
|
414
|
+
}
|
|
415
|
+
static scope(name, ...args) {
|
|
416
|
+
return this.query().scope(name, ...args);
|
|
417
|
+
}
|
|
418
|
+
static has(relationName, operator, count) {
|
|
419
|
+
return this.query().has(relationName, operator, count);
|
|
420
|
+
}
|
|
421
|
+
static whereHas(relationName, callback, operator, count) {
|
|
422
|
+
return this.query().whereHas(relationName, callback, operator, count);
|
|
423
|
+
}
|
|
424
|
+
static doesntHave(relationName) {
|
|
425
|
+
return this.query().doesntHave(relationName);
|
|
426
|
+
}
|
|
427
|
+
static withCount(relationName, alias) {
|
|
428
|
+
return this.query().withCount(relationName, alias);
|
|
429
|
+
}
|
|
430
|
+
static withSum(relationName, column, alias) {
|
|
431
|
+
return this.query().withSum(relationName, column, alias);
|
|
432
|
+
}
|
|
433
|
+
static withAvg(relationName, column, alias) {
|
|
434
|
+
return this.query().withAvg(relationName, column, alias);
|
|
435
|
+
}
|
|
436
|
+
static withMin(relationName, column, alias) {
|
|
437
|
+
return this.query().withMin(relationName, column, alias);
|
|
438
|
+
}
|
|
439
|
+
static withMax(relationName, column, alias) {
|
|
440
|
+
return this.query().withMax(relationName, column, alias);
|
|
441
|
+
}
|
|
442
|
+
static async all() {
|
|
443
|
+
return this.query().get();
|
|
444
|
+
}
|
|
445
|
+
static async paginate(perPage, page) {
|
|
446
|
+
return this.query().paginate(perPage, page);
|
|
447
|
+
}
|
|
448
|
+
static async eagerLoadRelations(models, relations) {
|
|
449
|
+
for (const relationName of relations) {
|
|
450
|
+
if (relationName.includes(".")) {
|
|
451
|
+
const [first, ...rest] = relationName.split(".");
|
|
452
|
+
await this.eagerLoadRelation(models, first);
|
|
453
|
+
const nestedModels = [];
|
|
454
|
+
for (const model of models) {
|
|
455
|
+
const related = model.getRelation(first);
|
|
456
|
+
if (Array.isArray(related))
|
|
457
|
+
nestedModels.push(...related);
|
|
458
|
+
else if (related)
|
|
459
|
+
nestedModels.push(related);
|
|
460
|
+
}
|
|
461
|
+
if (nestedModels.length > 0) {
|
|
462
|
+
await this.eagerLoadRelations(nestedModels, [rest.join(".")]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
await this.eagerLoadRelation(models, relationName);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
static async eagerLoadRelation(models, relationName) {
|
|
471
|
+
if (models.length === 0)
|
|
472
|
+
return;
|
|
473
|
+
const firstModel = models[0];
|
|
474
|
+
const relation = firstModel[relationName]();
|
|
475
|
+
relation.addEagerConstraints(models);
|
|
476
|
+
const results = await relation.getEager();
|
|
477
|
+
relation.match(models, results, relationName);
|
|
478
|
+
}
|
|
479
|
+
fill(attributes) {
|
|
480
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
481
|
+
if (this.isFillable(key)) {
|
|
482
|
+
this.setAttribute(key, value);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return this;
|
|
486
|
+
}
|
|
487
|
+
isFillable(key) {
|
|
488
|
+
const constructor = this.constructor;
|
|
489
|
+
if (constructor.fillable.length > 0) {
|
|
490
|
+
return constructor.fillable.includes(key);
|
|
491
|
+
}
|
|
492
|
+
if (constructor.guarded.length > 0) {
|
|
493
|
+
return !constructor.guarded.includes(key);
|
|
494
|
+
}
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
getAttribute(key) {
|
|
498
|
+
const value = this.$attributes[key];
|
|
499
|
+
return this.castAttribute(key, value);
|
|
500
|
+
}
|
|
501
|
+
setAttribute(key, value) {
|
|
502
|
+
this.$attributes[key] = this.serializeCastAttribute(key, value);
|
|
503
|
+
}
|
|
504
|
+
castAttribute(key, value) {
|
|
505
|
+
const cast = this.getCastDefinition(key);
|
|
506
|
+
if (!cast || value === null || value === undefined)
|
|
507
|
+
return value;
|
|
508
|
+
const custom = this.resolveCustomCast(cast);
|
|
509
|
+
if (custom)
|
|
510
|
+
return custom.get(this, key, value, this.$attributes);
|
|
511
|
+
const [type, argument] = String(cast).split(":");
|
|
512
|
+
switch (type) {
|
|
513
|
+
case "boolean":
|
|
514
|
+
case "bool":
|
|
515
|
+
return !!value;
|
|
516
|
+
case "number":
|
|
517
|
+
case "integer":
|
|
518
|
+
case "int":
|
|
519
|
+
case "float":
|
|
520
|
+
case "double":
|
|
521
|
+
return Number(value);
|
|
522
|
+
case "decimal":
|
|
523
|
+
return Number(value).toFixed(Number(argument || 2));
|
|
524
|
+
case "string":
|
|
525
|
+
return String(value);
|
|
526
|
+
case "date":
|
|
527
|
+
case "datetime":
|
|
528
|
+
return new Date(value);
|
|
529
|
+
case "json":
|
|
530
|
+
case "array":
|
|
531
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
532
|
+
case "object":
|
|
533
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
534
|
+
case "enum":
|
|
535
|
+
return value;
|
|
536
|
+
case "encrypted":
|
|
537
|
+
return typeof value === "string" ? Buffer.from(value, "base64").toString("utf8") : value;
|
|
538
|
+
default:
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
serializeCastAttribute(key, value) {
|
|
543
|
+
const cast = this.getCastDefinition(key);
|
|
544
|
+
if (!cast || value === null || value === undefined)
|
|
545
|
+
return value;
|
|
546
|
+
const custom = this.resolveCustomCast(cast);
|
|
547
|
+
if (custom)
|
|
548
|
+
return custom.set(this, key, value, this.$attributes);
|
|
549
|
+
const [type, argument] = String(cast).split(":");
|
|
550
|
+
switch (type) {
|
|
551
|
+
case "boolean":
|
|
552
|
+
case "bool":
|
|
553
|
+
return value ? 1 : 0;
|
|
554
|
+
case "number":
|
|
555
|
+
case "integer":
|
|
556
|
+
case "int":
|
|
557
|
+
case "float":
|
|
558
|
+
case "double":
|
|
559
|
+
return Number(value);
|
|
560
|
+
case "decimal":
|
|
561
|
+
return Number(value).toFixed(Number(argument || 2));
|
|
562
|
+
case "string":
|
|
563
|
+
return String(value);
|
|
564
|
+
case "date":
|
|
565
|
+
case "datetime":
|
|
566
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
567
|
+
case "json":
|
|
568
|
+
case "array":
|
|
569
|
+
case "object":
|
|
570
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
571
|
+
case "enum":
|
|
572
|
+
return typeof value === "object" && "value" in value ? value.value : value;
|
|
573
|
+
case "encrypted":
|
|
574
|
+
return Buffer.from(String(value), "utf8").toString("base64");
|
|
575
|
+
default:
|
|
576
|
+
return value;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
mergeCasts(casts) {
|
|
580
|
+
this.$casts = { ...this.$casts, ...casts };
|
|
581
|
+
return this;
|
|
582
|
+
}
|
|
583
|
+
getCastDefinition(key) {
|
|
584
|
+
const constructor = this.constructor;
|
|
585
|
+
return this.$casts[key] || constructor.casts[key];
|
|
586
|
+
}
|
|
587
|
+
resolveCustomCast(cast) {
|
|
588
|
+
if (typeof cast === "string")
|
|
589
|
+
return null;
|
|
590
|
+
if (typeof cast === "function")
|
|
591
|
+
return new cast();
|
|
592
|
+
if (typeof cast.get === "function" && typeof cast.set === "function")
|
|
593
|
+
return cast;
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
getDirty() {
|
|
597
|
+
const dirty = {};
|
|
598
|
+
for (const [key, value] of Object.entries(this.$attributes)) {
|
|
599
|
+
if (this.$original[key] !== value) {
|
|
600
|
+
dirty[key] = value;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return dirty;
|
|
604
|
+
}
|
|
605
|
+
isDirty() {
|
|
606
|
+
return Object.keys(this.getDirty()).length > 0;
|
|
607
|
+
}
|
|
608
|
+
async save() {
|
|
609
|
+
const constructor = this.constructor;
|
|
610
|
+
if (this.$exists) {
|
|
611
|
+
await ObserverRegistry.dispatch("updating", this);
|
|
612
|
+
await ObserverRegistry.dispatch("saving", this);
|
|
613
|
+
if (constructor.timestamps) {
|
|
614
|
+
this.$attributes["updated_at"] = this.freshTimestamp();
|
|
615
|
+
}
|
|
616
|
+
const dirty = this.getDirty();
|
|
617
|
+
if (Object.keys(dirty).length > 0) {
|
|
618
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
619
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
620
|
+
.where(constructor.primaryKey, pk)
|
|
621
|
+
.update(dirty);
|
|
622
|
+
}
|
|
623
|
+
this.$original = { ...this.$attributes };
|
|
624
|
+
await ObserverRegistry.dispatch("updated", this);
|
|
625
|
+
await ObserverRegistry.dispatch("saved", this);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
await ObserverRegistry.dispatch("creating", this);
|
|
629
|
+
await ObserverRegistry.dispatch("saving", this);
|
|
630
|
+
if (constructor.timestamps) {
|
|
631
|
+
const now = this.freshTimestamp();
|
|
632
|
+
this.$attributes["created_at"] = now;
|
|
633
|
+
this.$attributes["updated_at"] = now;
|
|
634
|
+
}
|
|
635
|
+
const result = await new Builder(constructor.getConnection(), constructor.getTable()).insertGetId(this.$attributes);
|
|
636
|
+
if (result) {
|
|
637
|
+
this.$attributes[constructor.primaryKey] = result;
|
|
638
|
+
}
|
|
639
|
+
this.$exists = true;
|
|
640
|
+
this.$original = { ...this.$attributes };
|
|
641
|
+
await ObserverRegistry.dispatch("created", this);
|
|
642
|
+
await ObserverRegistry.dispatch("saved", this);
|
|
643
|
+
}
|
|
644
|
+
return this;
|
|
645
|
+
}
|
|
646
|
+
async delete() {
|
|
647
|
+
const constructor = this.constructor;
|
|
648
|
+
await ObserverRegistry.dispatch("deleting", this);
|
|
649
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
650
|
+
if (!pk)
|
|
651
|
+
return false;
|
|
652
|
+
if (constructor.softDeletes) {
|
|
653
|
+
const deletedAt = this.freshTimestamp();
|
|
654
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
655
|
+
.where(constructor.primaryKey, pk)
|
|
656
|
+
.update({ [constructor.deletedAtColumn]: deletedAt });
|
|
657
|
+
this.$attributes[constructor.deletedAtColumn] = deletedAt;
|
|
658
|
+
this.$original = { ...this.$attributes };
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
662
|
+
.where(constructor.primaryKey, pk)
|
|
663
|
+
.delete();
|
|
664
|
+
this.$exists = false;
|
|
665
|
+
}
|
|
666
|
+
await ObserverRegistry.dispatch("deleted", this);
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
async restore() {
|
|
670
|
+
const constructor = this.constructor;
|
|
671
|
+
if (!constructor.softDeletes)
|
|
672
|
+
return false;
|
|
673
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
674
|
+
if (!pk)
|
|
675
|
+
return false;
|
|
676
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
677
|
+
.where(constructor.primaryKey, pk)
|
|
678
|
+
.update({ [constructor.deletedAtColumn]: null });
|
|
679
|
+
this.$attributes[constructor.deletedAtColumn] = null;
|
|
680
|
+
this.$original = { ...this.$attributes };
|
|
681
|
+
this.$exists = true;
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
async forceDelete() {
|
|
685
|
+
const constructor = this.constructor;
|
|
686
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
687
|
+
if (!pk)
|
|
688
|
+
return false;
|
|
689
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
690
|
+
.where(constructor.primaryKey, pk)
|
|
691
|
+
.delete();
|
|
692
|
+
this.$exists = false;
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
async refresh() {
|
|
696
|
+
const constructor = this.constructor;
|
|
697
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
698
|
+
if (!pk)
|
|
699
|
+
return this;
|
|
700
|
+
const result = await constructor.find(pk);
|
|
701
|
+
if (result) {
|
|
702
|
+
this.$attributes = result.$attributes;
|
|
703
|
+
this.$original = { ...result.$attributes };
|
|
704
|
+
}
|
|
705
|
+
return this;
|
|
706
|
+
}
|
|
707
|
+
toJSON() {
|
|
708
|
+
const result = {};
|
|
709
|
+
for (const key of Object.keys(this.$attributes)) {
|
|
710
|
+
result[key] = this.getAttribute(key);
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
toString() {
|
|
715
|
+
return JSON.stringify(this.toJSON());
|
|
716
|
+
}
|
|
717
|
+
freshTimestamp() {
|
|
718
|
+
return new Date().toISOString();
|
|
719
|
+
}
|
|
720
|
+
setRelation(name, value) {
|
|
721
|
+
this.$relations[name] = value;
|
|
722
|
+
}
|
|
723
|
+
getRelation(name) {
|
|
724
|
+
return this.$relations[name];
|
|
725
|
+
}
|
|
726
|
+
// Relations
|
|
727
|
+
hasMany(related, foreignKey, localKey) {
|
|
728
|
+
return new HasMany(this, related, foreignKey, localKey);
|
|
729
|
+
}
|
|
730
|
+
belongsTo(related, foreignKey, ownerKey) {
|
|
731
|
+
return new BelongsTo(this, related, foreignKey, ownerKey);
|
|
732
|
+
}
|
|
733
|
+
hasOne(related, foreignKey, localKey) {
|
|
734
|
+
return new HasOne(this, related, foreignKey, localKey);
|
|
735
|
+
}
|
|
736
|
+
hasManyThrough(related, through, firstKey, secondKey, localKey, secondLocalKey) {
|
|
737
|
+
return new HasManyThrough(this, related, through, firstKey, secondKey, localKey, secondLocalKey);
|
|
738
|
+
}
|
|
739
|
+
hasOneThrough(related, through, firstKey, secondKey, localKey, secondLocalKey) {
|
|
740
|
+
return new HasOneThrough(this, related, through, firstKey, secondKey, localKey, secondLocalKey);
|
|
741
|
+
}
|
|
742
|
+
belongsToMany(related, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
|
|
743
|
+
return new BelongsToMany(this, related, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey);
|
|
744
|
+
}
|
|
745
|
+
// Polymorphic relations
|
|
746
|
+
morphTo(name, typeMap) {
|
|
747
|
+
return new MorphTo(this, name, typeMap);
|
|
748
|
+
}
|
|
749
|
+
morphOne(related, name, typeColumn, idColumn, localKey) {
|
|
750
|
+
return new MorphOne(this, related, name, typeColumn, idColumn, localKey);
|
|
751
|
+
}
|
|
752
|
+
morphMany(related, name, typeColumn, idColumn, localKey) {
|
|
753
|
+
return new MorphMany(this, related, name, typeColumn, idColumn, localKey);
|
|
754
|
+
}
|
|
755
|
+
morphToMany(related, name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
|
|
756
|
+
const type = this.constructor.morphName || this.constructor.name;
|
|
757
|
+
return new MorphToMany(this, related, name, table, foreignPivotKey || `${snakeCase(name)}_id`, relatedPivotKey || `${snakeCase(related.name)}_id`, parentKey, relatedKey, type);
|
|
758
|
+
}
|
|
759
|
+
morphedByMany(related, name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
|
|
760
|
+
const type = related.morphName || related.name;
|
|
761
|
+
return new MorphToMany(this, related, name, table, foreignPivotKey || `${snakeCase(this.constructor.name)}_id`, relatedPivotKey || `${snakeCase(name)}_id`, parentKey, relatedKey, type);
|
|
762
|
+
}
|
|
763
|
+
}
|