outlet-orm 10.0.0 → 11.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/README.md +32 -0
- package/bin/reverse.js +0 -1
- package/package.json +1 -1
- package/skills/outlet-orm/ADVANCED.md +29 -0
- package/skills/outlet-orm/API.md +30 -0
- package/skills/outlet-orm/MODELS.md +144 -2
- package/skills/outlet-orm/QUERIES.md +133 -0
- package/skills/outlet-orm/RELATIONS.md +44 -0
- package/skills/outlet-orm/SKILL.md +4 -2
- package/skills/outlet-orm/TYPESCRIPT.md +98 -0
- package/src/AI/AIManager.js +58 -58
- package/src/AI/AIQueryBuilder.js +2 -2
- package/src/AI/AIQueryOptimizer.js +2 -2
- package/src/AI/Contracts/AudioProviderContract.js +2 -2
- package/src/AI/Contracts/ChatProviderContract.js +3 -2
- package/src/AI/Contracts/EmbeddingsProviderContract.js +1 -1
- package/src/AI/Contracts/ImageProviderContract.js +1 -1
- package/src/AI/Contracts/ModelsProviderContract.js +1 -1
- package/src/AI/Contracts/ToolContract.js +1 -1
- package/src/AI/MCPServer.js +3 -3
- package/src/AI/Providers/CustomOpenAIProvider.js +0 -2
- package/src/AI/Providers/GeminiProvider.js +2 -2
- package/src/AI/Providers/OpenAIProvider.js +0 -5
- package/src/AI/Support/DocumentAttachmentMapper.js +37 -37
- package/src/AI/Support/FileSecurity.js +1 -1
- package/src/Backup/BackupManager.js +6 -6
- package/src/Backup/BackupScheduler.js +1 -1
- package/src/Backup/BackupSocketServer.js +2 -2
- package/src/DatabaseConnection.js +51 -0
- package/src/Model.js +245 -5
- package/src/QueryBuilder.js +191 -0
- package/src/Relations/HasOneRelation.js +114 -114
- package/src/Relations/HasOneThroughRelation.js +105 -105
- package/src/Relations/MorphOneRelation.js +4 -2
- package/src/Relations/Relation.js +35 -0
- package/types/index.d.ts +67 -1
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
const Relation = require('./Relation');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Has One Relation
|
|
5
|
-
* Represents a one-to-one relationship
|
|
6
|
-
*/
|
|
7
|
-
class HasOneRelation extends Relation {
|
|
8
|
-
/**
|
|
9
|
-
* Get the related model
|
|
10
|
-
* @returns {Promise<Model|null>}
|
|
11
|
-
*/
|
|
12
|
-
async get() {
|
|
13
|
-
const result = await this.related
|
|
14
|
-
.where(this.foreignKey, this.parent.getAttribute(this.localKey))
|
|
15
|
-
.first();
|
|
16
|
-
|
|
17
|
-
return result;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Eager load the relationship for a collection of parent models
|
|
22
|
-
* @param {Array<Model>} models
|
|
23
|
-
* @param {string} relationName
|
|
24
|
-
* @returns {Promise<void>}
|
|
25
|
-
*/
|
|
26
|
-
async eagerLoad(models, relationName, constraint) {
|
|
27
|
-
const keys = models
|
|
28
|
-
.map(model => model.getAttribute(this.localKey))
|
|
29
|
-
.filter(key => key !== null && key !== undefined);
|
|
30
|
-
|
|
31
|
-
if (keys.length === 0) return;
|
|
32
|
-
|
|
33
|
-
const qb = this.related.whereIn(this.foreignKey, keys);
|
|
34
|
-
if (typeof constraint === 'function') constraint(qb);
|
|
35
|
-
const relatedModels = await qb.get();
|
|
36
|
-
|
|
37
|
-
const relatedMap = {};
|
|
38
|
-
relatedModels.forEach(model => {
|
|
39
|
-
const foreignKeyValue = model.getAttribute(this.foreignKey);
|
|
40
|
-
relatedMap[foreignKeyValue] = model;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
models.forEach(model => {
|
|
44
|
-
const localKeyValue = model.getAttribute(this.localKey);
|
|
45
|
-
model.relations[relationName] = relatedMap[localKeyValue] ||
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Add a where clause to the relation query
|
|
51
|
-
* @param {string} column
|
|
52
|
-
* @param {string|any} operator
|
|
53
|
-
* @param {any} value
|
|
54
|
-
* @returns {QueryBuilder}
|
|
55
|
-
*/
|
|
56
|
-
where(column, operator, value) {
|
|
57
|
-
return this.related
|
|
58
|
-
.where(this.foreignKey, this.parent.getAttribute(this.localKey))
|
|
59
|
-
.where(column, operator, value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Create a new related model and associate it
|
|
64
|
-
* @param {Object} attributes
|
|
65
|
-
* @returns {Promise<Model>}
|
|
66
|
-
*/
|
|
67
|
-
async create(attributes = {}) {
|
|
68
|
-
const model = new this.related.model(attributes);
|
|
69
|
-
model.setAttribute(this.foreignKey, this.parent.getAttribute(this.localKey));
|
|
70
|
-
await model.save();
|
|
71
|
-
return model;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Save an existing model and associate it
|
|
76
|
-
* @param {Model} model
|
|
77
|
-
* @returns {Promise<Model>}
|
|
78
|
-
*/
|
|
79
|
-
async save(model) {
|
|
80
|
-
model.setAttribute(this.foreignKey, this.parent.getAttribute(this.localKey));
|
|
81
|
-
await model.save();
|
|
82
|
-
return model;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create multiple related models and associate them
|
|
87
|
-
* @param {Array<Object>} attributesArray
|
|
88
|
-
* @returns {Promise<Array<Model>>}
|
|
89
|
-
*/
|
|
90
|
-
async createMany(attributesArray) {
|
|
91
|
-
const models = [];
|
|
92
|
-
for (const attributes of attributesArray) {
|
|
93
|
-
const model = await this.create(attributes);
|
|
94
|
-
models.push(model);
|
|
95
|
-
}
|
|
96
|
-
return models;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Save multiple existing models and associate them
|
|
101
|
-
* @param {Array<Model>} models
|
|
102
|
-
* @returns {Promise<Array<Model>>}
|
|
103
|
-
*/
|
|
104
|
-
async saveMany(models) {
|
|
105
|
-
const savedModels = [];
|
|
106
|
-
for (const model of models) {
|
|
107
|
-
const saved = await this.save(model);
|
|
108
|
-
savedModels.push(saved);
|
|
109
|
-
}
|
|
110
|
-
return savedModels;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = HasOneRelation;
|
|
1
|
+
const Relation = require('./Relation');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Has One Relation
|
|
5
|
+
* Represents a one-to-one relationship
|
|
6
|
+
*/
|
|
7
|
+
class HasOneRelation extends Relation {
|
|
8
|
+
/**
|
|
9
|
+
* Get the related model
|
|
10
|
+
* @returns {Promise<Model|null>}
|
|
11
|
+
*/
|
|
12
|
+
async get() {
|
|
13
|
+
const result = await this.related
|
|
14
|
+
.where(this.foreignKey, this.parent.getAttribute(this.localKey))
|
|
15
|
+
.first();
|
|
16
|
+
|
|
17
|
+
return result || this._buildDefault();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Eager load the relationship for a collection of parent models
|
|
22
|
+
* @param {Array<Model>} models
|
|
23
|
+
* @param {string} relationName
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
async eagerLoad(models, relationName, constraint) {
|
|
27
|
+
const keys = models
|
|
28
|
+
.map(model => model.getAttribute(this.localKey))
|
|
29
|
+
.filter(key => key !== null && key !== undefined);
|
|
30
|
+
|
|
31
|
+
if (keys.length === 0) return;
|
|
32
|
+
|
|
33
|
+
const qb = this.related.whereIn(this.foreignKey, keys);
|
|
34
|
+
if (typeof constraint === 'function') constraint(qb);
|
|
35
|
+
const relatedModels = await qb.get();
|
|
36
|
+
|
|
37
|
+
const relatedMap = {};
|
|
38
|
+
relatedModels.forEach(model => {
|
|
39
|
+
const foreignKeyValue = model.getAttribute(this.foreignKey);
|
|
40
|
+
relatedMap[foreignKeyValue] = model;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
models.forEach(model => {
|
|
44
|
+
const localKeyValue = model.getAttribute(this.localKey);
|
|
45
|
+
model.relations[relationName] = relatedMap[localKeyValue] || this._buildDefault();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add a where clause to the relation query
|
|
51
|
+
* @param {string} column
|
|
52
|
+
* @param {string|any} operator
|
|
53
|
+
* @param {any} value
|
|
54
|
+
* @returns {QueryBuilder}
|
|
55
|
+
*/
|
|
56
|
+
where(column, operator, value) {
|
|
57
|
+
return this.related
|
|
58
|
+
.where(this.foreignKey, this.parent.getAttribute(this.localKey))
|
|
59
|
+
.where(column, operator, value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a new related model and associate it
|
|
64
|
+
* @param {Object} attributes
|
|
65
|
+
* @returns {Promise<Model>}
|
|
66
|
+
*/
|
|
67
|
+
async create(attributes = {}) {
|
|
68
|
+
const model = new this.related.model(attributes);
|
|
69
|
+
model.setAttribute(this.foreignKey, this.parent.getAttribute(this.localKey));
|
|
70
|
+
await model.save();
|
|
71
|
+
return model;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Save an existing model and associate it
|
|
76
|
+
* @param {Model} model
|
|
77
|
+
* @returns {Promise<Model>}
|
|
78
|
+
*/
|
|
79
|
+
async save(model) {
|
|
80
|
+
model.setAttribute(this.foreignKey, this.parent.getAttribute(this.localKey));
|
|
81
|
+
await model.save();
|
|
82
|
+
return model;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create multiple related models and associate them
|
|
87
|
+
* @param {Array<Object>} attributesArray
|
|
88
|
+
* @returns {Promise<Array<Model>>}
|
|
89
|
+
*/
|
|
90
|
+
async createMany(attributesArray) {
|
|
91
|
+
const models = [];
|
|
92
|
+
for (const attributes of attributesArray) {
|
|
93
|
+
const model = await this.create(attributes);
|
|
94
|
+
models.push(model);
|
|
95
|
+
}
|
|
96
|
+
return models;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Save multiple existing models and associate them
|
|
101
|
+
* @param {Array<Model>} models
|
|
102
|
+
* @returns {Promise<Array<Model>>}
|
|
103
|
+
*/
|
|
104
|
+
async saveMany(models) {
|
|
105
|
+
const savedModels = [];
|
|
106
|
+
for (const model of models) {
|
|
107
|
+
const saved = await this.save(model);
|
|
108
|
+
savedModels.push(saved);
|
|
109
|
+
}
|
|
110
|
+
return savedModels;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = HasOneRelation;
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
const Relation = require('./Relation');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Has One Through Relation
|
|
5
|
-
* parent -> through -> related (final) - returns single model
|
|
6
|
-
*/
|
|
7
|
-
class HasOneThroughRelation extends Relation {
|
|
8
|
-
/**
|
|
9
|
-
* @param {import('../Model')} parent
|
|
10
|
-
* @param {typeof import('../Model')} relatedFinal
|
|
11
|
-
* @param {typeof import('../Model')} through
|
|
12
|
-
* @param {string} [foreignKeyOnThrough] - FK on through referencing parent
|
|
13
|
-
* @param {string} [throughKeyOnFinal] - FK on final referencing through
|
|
14
|
-
* @param {string} [localKey] - PK on parent
|
|
15
|
-
* @param {string} [throughLocalKey] - PK on through
|
|
16
|
-
*/
|
|
17
|
-
constructor(parent, relatedFinal, through, foreignKeyOnThrough, throughKeyOnFinal, localKey, throughLocalKey) {
|
|
18
|
-
super(parent, relatedFinal, foreignKeyOnThrough, localKey);
|
|
19
|
-
this.through = through;
|
|
20
|
-
// Defaults based on naming conventions
|
|
21
|
-
this.localKey = localKey || parent.constructor.primaryKey || 'id';
|
|
22
|
-
this.throughLocalKey = throughLocalKey || through.primaryKey || 'id';
|
|
23
|
-
this.foreignKeyOnThrough = foreignKeyOnThrough || `${parent.constructor.table.slice(0, -1)}_id`;
|
|
24
|
-
this.throughKeyOnFinal = throughKeyOnFinal || `${through.table.slice(0, -1)}_id`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get final related model (single)
|
|
29
|
-
* @returns {Promise<import('../Model')|null>}
|
|
30
|
-
*/
|
|
31
|
-
async get() {
|
|
32
|
-
const parentKeyValue = this.parent.getAttribute(this.localKey);
|
|
33
|
-
if (parentKeyValue === undefined || parentKeyValue === null) return
|
|
34
|
-
|
|
35
|
-
const throughRow = await this.through
|
|
36
|
-
.where(this.foreignKeyOnThrough, parentKeyValue)
|
|
37
|
-
.first();
|
|
38
|
-
|
|
39
|
-
if (!throughRow) return
|
|
40
|
-
|
|
41
|
-
const throughId = throughRow.getAttribute(this.throughLocalKey);
|
|
42
|
-
const result = await this.related
|
|
43
|
-
.where(this.throughKeyOnFinal, throughId)
|
|
44
|
-
.first();
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Eager load hasOneThrough for a batch of parents
|
|
50
|
-
* @param {Array} models
|
|
51
|
-
* @param {string} relationName
|
|
52
|
-
* @param {(qb: any) => void} [constraint]
|
|
53
|
-
*/
|
|
54
|
-
async eagerLoad(models, relationName, constraint) {
|
|
55
|
-
const parentKeys = models
|
|
56
|
-
.map(m => m.getAttribute(this.localKey))
|
|
57
|
-
.filter(v => v !== undefined && v !== null);
|
|
58
|
-
|
|
59
|
-
if (parentKeys.length === 0) {
|
|
60
|
-
models.forEach(m => { m.relations[relationName] = null; });
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Fetch through rows for all parent keys
|
|
65
|
-
const throughRows = await this.through
|
|
66
|
-
.whereIn(this.foreignKeyOnThrough, parentKeys)
|
|
67
|
-
.get();
|
|
68
|
-
|
|
69
|
-
if (throughRows.length === 0) {
|
|
70
|
-
models.forEach(m => { m.relations[relationName] = null; });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Map parentKey -> throughId (assuming one through per parent)
|
|
75
|
-
const parentToThroughId = {};
|
|
76
|
-
for (const row of throughRows) {
|
|
77
|
-
const pKey = row.getAttribute(this.foreignKeyOnThrough);
|
|
78
|
-
const tId = row.getAttribute(this.throughLocalKey);
|
|
79
|
-
parentToThroughId[pKey] = tId; // overwrite if multiple, take last
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const allThroughIds = Object.values(parentToThroughId);
|
|
83
|
-
|
|
84
|
-
// Fetch finals in one query (with optional constraint)
|
|
85
|
-
const qb = this.related.whereIn(this.throughKeyOnFinal, allThroughIds);
|
|
86
|
-
if (typeof constraint === 'function') constraint(qb);
|
|
87
|
-
const finals = await qb.get();
|
|
88
|
-
|
|
89
|
-
// Map through id to final
|
|
90
|
-
const finalsByThrough = {};
|
|
91
|
-
for (const f of finals) {
|
|
92
|
-
const key = f.getAttribute(this.throughKeyOnFinal);
|
|
93
|
-
finalsByThrough[key] = f; // assume one per through
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Assign to each parent
|
|
97
|
-
for (const m of models) {
|
|
98
|
-
const pKey = m.getAttribute(this.localKey);
|
|
99
|
-
const tId = parentToThroughId[pKey];
|
|
100
|
-
m.relations[relationName] = tId ? finalsByThrough[tId] || null : null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
module.exports = HasOneThroughRelation;
|
|
1
|
+
const Relation = require('./Relation');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Has One Through Relation
|
|
5
|
+
* parent -> through -> related (final) - returns single model
|
|
6
|
+
*/
|
|
7
|
+
class HasOneThroughRelation extends Relation {
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('../Model')} parent
|
|
10
|
+
* @param {typeof import('../Model')} relatedFinal
|
|
11
|
+
* @param {typeof import('../Model')} through
|
|
12
|
+
* @param {string} [foreignKeyOnThrough] - FK on through referencing parent
|
|
13
|
+
* @param {string} [throughKeyOnFinal] - FK on final referencing through
|
|
14
|
+
* @param {string} [localKey] - PK on parent
|
|
15
|
+
* @param {string} [throughLocalKey] - PK on through
|
|
16
|
+
*/
|
|
17
|
+
constructor(parent, relatedFinal, through, foreignKeyOnThrough, throughKeyOnFinal, localKey, throughLocalKey) {
|
|
18
|
+
super(parent, relatedFinal, foreignKeyOnThrough, localKey);
|
|
19
|
+
this.through = through;
|
|
20
|
+
// Defaults based on naming conventions
|
|
21
|
+
this.localKey = localKey || parent.constructor.primaryKey || 'id';
|
|
22
|
+
this.throughLocalKey = throughLocalKey || through.primaryKey || 'id';
|
|
23
|
+
this.foreignKeyOnThrough = foreignKeyOnThrough || `${parent.constructor.table.slice(0, -1)}_id`;
|
|
24
|
+
this.throughKeyOnFinal = throughKeyOnFinal || `${through.table.slice(0, -1)}_id`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get final related model (single)
|
|
29
|
+
* @returns {Promise<import('../Model')|null>}
|
|
30
|
+
*/
|
|
31
|
+
async get() {
|
|
32
|
+
const parentKeyValue = this.parent.getAttribute(this.localKey);
|
|
33
|
+
if (parentKeyValue === undefined || parentKeyValue === null) return this._buildDefault();
|
|
34
|
+
|
|
35
|
+
const throughRow = await this.through
|
|
36
|
+
.where(this.foreignKeyOnThrough, parentKeyValue)
|
|
37
|
+
.first();
|
|
38
|
+
|
|
39
|
+
if (!throughRow) return this._buildDefault();
|
|
40
|
+
|
|
41
|
+
const throughId = throughRow.getAttribute(this.throughLocalKey);
|
|
42
|
+
const result = await this.related
|
|
43
|
+
.where(this.throughKeyOnFinal, throughId)
|
|
44
|
+
.first();
|
|
45
|
+
return result || this._buildDefault();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Eager load hasOneThrough for a batch of parents
|
|
50
|
+
* @param {Array} models
|
|
51
|
+
* @param {string} relationName
|
|
52
|
+
* @param {(qb: any) => void} [constraint]
|
|
53
|
+
*/
|
|
54
|
+
async eagerLoad(models, relationName, constraint) {
|
|
55
|
+
const parentKeys = models
|
|
56
|
+
.map(m => m.getAttribute(this.localKey))
|
|
57
|
+
.filter(v => v !== undefined && v !== null);
|
|
58
|
+
|
|
59
|
+
if (parentKeys.length === 0) {
|
|
60
|
+
models.forEach(m => { m.relations[relationName] = null; });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fetch through rows for all parent keys
|
|
65
|
+
const throughRows = await this.through
|
|
66
|
+
.whereIn(this.foreignKeyOnThrough, parentKeys)
|
|
67
|
+
.get();
|
|
68
|
+
|
|
69
|
+
if (throughRows.length === 0) {
|
|
70
|
+
models.forEach(m => { m.relations[relationName] = null; });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Map parentKey -> throughId (assuming one through per parent)
|
|
75
|
+
const parentToThroughId = {};
|
|
76
|
+
for (const row of throughRows) {
|
|
77
|
+
const pKey = row.getAttribute(this.foreignKeyOnThrough);
|
|
78
|
+
const tId = row.getAttribute(this.throughLocalKey);
|
|
79
|
+
parentToThroughId[pKey] = tId; // overwrite if multiple, take last
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const allThroughIds = Object.values(parentToThroughId);
|
|
83
|
+
|
|
84
|
+
// Fetch finals in one query (with optional constraint)
|
|
85
|
+
const qb = this.related.whereIn(this.throughKeyOnFinal, allThroughIds);
|
|
86
|
+
if (typeof constraint === 'function') constraint(qb);
|
|
87
|
+
const finals = await qb.get();
|
|
88
|
+
|
|
89
|
+
// Map through id to final
|
|
90
|
+
const finalsByThrough = {};
|
|
91
|
+
for (const f of finals) {
|
|
92
|
+
const key = f.getAttribute(this.throughKeyOnFinal);
|
|
93
|
+
finalsByThrough[key] = f; // assume one per through
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Assign to each parent
|
|
97
|
+
for (const m of models) {
|
|
98
|
+
const pKey = m.getAttribute(this.localKey);
|
|
99
|
+
const tId = parentToThroughId[pKey];
|
|
100
|
+
m.relations[relationName] = tId ? finalsByThrough[tId] || null : null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = HasOneThroughRelation;
|
|
@@ -16,10 +16,12 @@ class MorphOneRelation extends Relation {
|
|
|
16
16
|
* @returns {Promise<Model|null>}
|
|
17
17
|
*/
|
|
18
18
|
async get() {
|
|
19
|
-
|
|
19
|
+
const result = await this.related
|
|
20
20
|
.where(this.foreignKey, this.parent.getAttribute(this.localKey))
|
|
21
21
|
.where(`${this.morphType}_type`, this.parent.constructor.table)
|
|
22
22
|
.first();
|
|
23
|
+
|
|
24
|
+
return result || this._buildDefault();
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -46,7 +48,7 @@ class MorphOneRelation extends Relation {
|
|
|
46
48
|
|
|
47
49
|
for (const model of models) {
|
|
48
50
|
const key = model.getAttribute(this.localKey);
|
|
49
|
-
model.relations[relationName] = relatedMap[key] ||
|
|
51
|
+
model.relations[relationName] = relatedMap[key] || this._buildDefault();
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -7,6 +7,41 @@ class Relation {
|
|
|
7
7
|
this.related = related;
|
|
8
8
|
this.foreignKey = foreignKey;
|
|
9
9
|
this.localKey = localKey;
|
|
10
|
+
this._defaultValue = undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Set a default value/model to return when the relation is empty
|
|
15
|
+
* @param {Object|Function|boolean} [value=true] - Default attributes, factory fn, or true for empty model
|
|
16
|
+
* @returns {this}
|
|
17
|
+
*/
|
|
18
|
+
withDefault(value = true) {
|
|
19
|
+
this._defaultValue = value;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the default model when the relation result is null
|
|
25
|
+
* @returns {Model|null}
|
|
26
|
+
* @protected
|
|
27
|
+
*/
|
|
28
|
+
_buildDefault() {
|
|
29
|
+
if (this._defaultValue === undefined) return null;
|
|
30
|
+
if (typeof this._defaultValue === 'function') return this._defaultValue();
|
|
31
|
+
if (this._defaultValue === true) {
|
|
32
|
+
// Return an empty instance of the related model
|
|
33
|
+
const RelatedModel = typeof this.related === 'function' && this.related.prototype
|
|
34
|
+
? this.related
|
|
35
|
+
: this.related.constructor;
|
|
36
|
+
return new RelatedModel();
|
|
37
|
+
}
|
|
38
|
+
if (typeof this._defaultValue === 'object' && this._defaultValue !== null) {
|
|
39
|
+
const RelatedModel = typeof this.related === 'function' && this.related.prototype
|
|
40
|
+
? this.related
|
|
41
|
+
: this.related.constructor;
|
|
42
|
+
return new RelatedModel(this._defaultValue);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
10
45
|
}
|
|
11
46
|
|
|
12
47
|
/**
|
package/types/index.d.ts
CHANGED
|
@@ -88,6 +88,8 @@ declare module 'outlet-orm' {
|
|
|
88
88
|
update(table: string, data: Record<string, any>, query: QueryObject): Promise<UpdateResult>;
|
|
89
89
|
delete(table: string, query: QueryObject): Promise<DeleteResult>;
|
|
90
90
|
count(table: string, query: QueryObject): Promise<number>;
|
|
91
|
+
/** Execute an aggregate function (SUM, AVG, MIN, MAX) */
|
|
92
|
+
aggregate(table: string, fn: 'SUM' | 'AVG' | 'MIN' | 'MAX', column: string, query: QueryObject): Promise<number>;
|
|
91
93
|
executeRawQuery(sql: string, params?: any[]): Promise<any[]>;
|
|
92
94
|
/** Execute raw SQL and return driver-native results (used by migrations) */
|
|
93
95
|
execute(sql: string, params?: any[]): Promise<any>;
|
|
@@ -237,6 +239,37 @@ declare module 'outlet-orm' {
|
|
|
237
239
|
/** Lazily iterate over matching records using an async generator */
|
|
238
240
|
cursor(chunkSize?: number): AsyncGenerator<T, void, unknown>;
|
|
239
241
|
|
|
242
|
+
// Convenience query methods
|
|
243
|
+
/** Get an array of values for a single column, optionally keyed by another column */
|
|
244
|
+
pluck(column: string): Promise<any[]>;
|
|
245
|
+
pluck(column: string, keyColumn: string): Promise<Record<string, any>>;
|
|
246
|
+
/** Get the value of a single column from the first matching row */
|
|
247
|
+
value(column: string): Promise<any>;
|
|
248
|
+
|
|
249
|
+
// Aggregate methods
|
|
250
|
+
/** Get the sum of a column */
|
|
251
|
+
sum(column: string): Promise<number>;
|
|
252
|
+
/** Get the average of a column */
|
|
253
|
+
avg(column: string): Promise<number>;
|
|
254
|
+
/** Get the minimum value of a column */
|
|
255
|
+
min(column: string): Promise<number>;
|
|
256
|
+
/** Get the maximum value of a column */
|
|
257
|
+
max(column: string): Promise<number>;
|
|
258
|
+
|
|
259
|
+
// Batch & conditional methods
|
|
260
|
+
/** Process results in chunks */
|
|
261
|
+
chunk(size: number, callback: (chunk: T[], page: number) => void | false | Promise<void | false>): Promise<void>;
|
|
262
|
+
/** Conditionally apply a callback to the query */
|
|
263
|
+
when(condition: any, callback: (qb: this, value: any) => void, fallback?: (qb: this, value: any) => void): this;
|
|
264
|
+
/** Pass the query builder to a callback without modifying the chain */
|
|
265
|
+
tap(callback: (qb: this) => void): this;
|
|
266
|
+
|
|
267
|
+
// Debugging
|
|
268
|
+
/** Get the SQL representation of the current query */
|
|
269
|
+
toSQL(): { table: string } & QueryObject;
|
|
270
|
+
/** Dump the SQL and throw */
|
|
271
|
+
dd(): never;
|
|
272
|
+
|
|
240
273
|
clone(): QueryBuilder<T>;
|
|
241
274
|
}
|
|
242
275
|
|
|
@@ -279,10 +312,15 @@ declare module 'outlet-orm' {
|
|
|
279
312
|
export type EventCallback<T extends Model = Model> = (model: T) => boolean | void | Promise<boolean | void>;
|
|
280
313
|
|
|
281
314
|
/**
|
|
282
|
-
* Base Model class with optional generic for typed attributes
|
|
315
|
+
* Base Model class with optional generic for typed attributes.
|
|
316
|
+
* Instances are wrapped in a Proxy so attributes can be accessed
|
|
317
|
+
* directly as properties: `user.name` / `user.name = 'Jean'`.
|
|
318
|
+
* Existing methods (getAttribute, setAttribute) remain available.
|
|
283
319
|
* @template TAttributes - Type of model attributes (defaults to Record<string, any>)
|
|
284
320
|
*/
|
|
285
321
|
export class Model<TAttributes extends Record<string, any> = Record<string, any>> {
|
|
322
|
+
/** Property-style attribute access via Proxy (read & write) */
|
|
323
|
+
[K: string]: any;
|
|
286
324
|
static table: string;
|
|
287
325
|
static primaryKey: string;
|
|
288
326
|
static timestamps: boolean;
|
|
@@ -290,6 +328,8 @@ declare module 'outlet-orm' {
|
|
|
290
328
|
static hidden: string[];
|
|
291
329
|
static casts: Record<string, CastType>;
|
|
292
330
|
static connection: DatabaseConnection | null;
|
|
331
|
+
/** Computed attributes to append in serialization */
|
|
332
|
+
static appends: string[];
|
|
293
333
|
|
|
294
334
|
// Soft Deletes
|
|
295
335
|
static softDeletes: boolean;
|
|
@@ -409,6 +449,30 @@ declare module 'outlet-orm' {
|
|
|
409
449
|
/** Load relations on an existing instance. Supports dot-notation and arrays. */
|
|
410
450
|
load(...relations: string[] | [string[]]): Promise<this>;
|
|
411
451
|
|
|
452
|
+
// Model instance utilities
|
|
453
|
+
/** Reload a fresh model instance from the database */
|
|
454
|
+
fresh(...relations: string[]): Promise<this | null>;
|
|
455
|
+
/** Reload attributes from DB into this instance */
|
|
456
|
+
refresh(): Promise<this>;
|
|
457
|
+
/** Clone the model without the primary key */
|
|
458
|
+
replicate(...except: string[]): this;
|
|
459
|
+
/** Check if two models have the same ID and table */
|
|
460
|
+
is(model: Model | null): boolean;
|
|
461
|
+
/** Check if two models are different */
|
|
462
|
+
isNot(model: Model | null): boolean;
|
|
463
|
+
/** Get a subset of attributes as a plain object */
|
|
464
|
+
only(...keys: (string | string[])[]): Partial<TAttributes>;
|
|
465
|
+
/** Get all attributes except specified keys */
|
|
466
|
+
except(...keys: (string | string[])[]): Partial<TAttributes>;
|
|
467
|
+
/** Make attributes visible on this instance */
|
|
468
|
+
makeVisible(...attrs: (string | string[])[]): this;
|
|
469
|
+
/** Make attributes hidden on this instance */
|
|
470
|
+
makeHidden(...attrs: (string | string[])[]): this;
|
|
471
|
+
/** Check if attribute was changed on last save */
|
|
472
|
+
wasChanged(attr?: string): boolean;
|
|
473
|
+
/** Get attributes changed on last save */
|
|
474
|
+
getChanges(): Partial<TAttributes>;
|
|
475
|
+
|
|
412
476
|
// Soft delete instance methods
|
|
413
477
|
trashed(): boolean;
|
|
414
478
|
restore(): Promise<this>;
|
|
@@ -459,6 +523,8 @@ declare module 'outlet-orm' {
|
|
|
459
523
|
|
|
460
524
|
export abstract class Relation<T extends Model> {
|
|
461
525
|
constructor(parent: Model, related: new () => T, foreignKey: string, localKey: string);
|
|
526
|
+
/** Set a default value when the relation result is null */
|
|
527
|
+
withDefault(value?: Record<string, any> | (() => T) | boolean): this;
|
|
462
528
|
abstract get(): Promise<T | T[] | null>;
|
|
463
529
|
abstract eagerLoad(models: Model[], relationName: string, constraint?: (qb: QueryBuilder<T>) => void): Promise<void>;
|
|
464
530
|
}
|