create-phoenixjs 0.1.3 → 0.1.5
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/package.json +1 -1
- package/template/app/controllers/HealthController.ts +22 -0
- package/template/app/gateways/EchoGateway.ts +58 -0
- package/template/bootstrap/app.ts +2 -7
- package/template/config/app.ts +12 -0
- package/template/config/database.ts +13 -1
- package/template/database/migrations/2024_01_01_000000_create_test_users_cli_table.ts +16 -0
- package/template/database/migrations/20260108164611_TestCliMigration.ts +16 -0
- package/template/database/migrations/2026_01_08_16_46_11_CreateTestMigrationsTable.ts +21 -0
- package/template/framework/cli/artisan.ts +12 -0
- package/template/framework/core/Application.ts +15 -0
- package/template/framework/database/DatabaseManager.ts +133 -0
- package/template/framework/database/connection/Connection.ts +71 -0
- package/template/framework/database/connection/ConnectionFactory.ts +30 -0
- package/template/framework/database/connection/PostgresConnection.ts +159 -0
- package/template/framework/database/console/MakeMigrationCommand.ts +58 -0
- package/template/framework/database/console/MigrateCommand.ts +32 -0
- package/template/framework/database/console/MigrateResetCommand.ts +31 -0
- package/template/framework/database/console/MigrateRollbackCommand.ts +31 -0
- package/template/framework/database/console/MigrateStatusCommand.ts +38 -0
- package/template/framework/database/migrations/DatabaseMigrationRepository.ts +122 -0
- package/template/framework/database/migrations/Migration.ts +5 -0
- package/template/framework/database/migrations/MigrationRepository.ts +46 -0
- package/template/framework/database/migrations/Migrator.ts +249 -0
- package/template/framework/database/migrations/index.ts +4 -0
- package/template/framework/database/orm/BelongsTo.ts +246 -0
- package/template/framework/database/orm/BelongsToMany.ts +570 -0
- package/template/framework/database/orm/Builder.ts +160 -0
- package/template/framework/database/orm/EagerLoadingBuilder.ts +324 -0
- package/template/framework/database/orm/HasMany.ts +303 -0
- package/template/framework/database/orm/HasManyThrough.ts +282 -0
- package/template/framework/database/orm/HasOne.ts +201 -0
- package/template/framework/database/orm/HasOneThrough.ts +281 -0
- package/template/framework/database/orm/Model.ts +1766 -0
- package/template/framework/database/orm/Relation.ts +342 -0
- package/template/framework/database/orm/Scope.ts +14 -0
- package/template/framework/database/orm/SoftDeletes.ts +160 -0
- package/template/framework/database/orm/index.ts +54 -0
- package/template/framework/database/orm/scopes/SoftDeletingScope.ts +58 -0
- package/template/framework/database/pagination/LengthAwarePaginator.ts +55 -0
- package/template/framework/database/pagination/Paginator.ts +110 -0
- package/template/framework/database/pagination/index.ts +2 -0
- package/template/framework/database/query/Builder.ts +918 -0
- package/template/framework/database/query/DB.ts +139 -0
- package/template/framework/database/query/grammars/Grammar.ts +430 -0
- package/template/framework/database/query/grammars/PostgresGrammar.ts +224 -0
- package/template/framework/database/query/grammars/index.ts +6 -0
- package/template/framework/database/query/index.ts +8 -0
- package/template/framework/database/query/types.ts +196 -0
- package/template/framework/database/schema/Blueprint.ts +478 -0
- package/template/framework/database/schema/Schema.ts +149 -0
- package/template/framework/database/schema/SchemaBuilder.ts +152 -0
- package/template/framework/database/schema/grammars/PostgresSchemaGrammar.ts +293 -0
- package/template/framework/database/schema/grammars/index.ts +5 -0
- package/template/framework/database/schema/index.ts +9 -0
- package/template/framework/log/Logger.ts +195 -0
- package/template/package.json +4 -1
- package/template/routes/api.ts +13 -35
- package/template/app/controllers/ExampleController.ts +0 -61
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { Relation } from './Relation';
|
|
2
|
+
import type { Model } from './Model';
|
|
3
|
+
import type { Binding } from '../query/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HasOneThrough Relationship - Retrieves a single related model through an intermediate model.
|
|
7
|
+
*
|
|
8
|
+
* This is useful when you need to access data that is related through another table.
|
|
9
|
+
*
|
|
10
|
+
* Example Schema:
|
|
11
|
+
* - countries table: id, name
|
|
12
|
+
* - users table: id, country_id, name
|
|
13
|
+
* - profiles table: id, user_id, bio
|
|
14
|
+
*
|
|
15
|
+
* A Country has one Profile through User.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* class Country extends Model {
|
|
20
|
+
* userProfile() {
|
|
21
|
+
* return this.hasOneThrough(
|
|
22
|
+
* Profile, // Final related model
|
|
23
|
+
* User, // Intermediate model
|
|
24
|
+
* 'country_id', // Foreign key on users table (links to countries)
|
|
25
|
+
* 'user_id', // Foreign key on profiles table (links to users)
|
|
26
|
+
* 'id', // Local key on countries table
|
|
27
|
+
* 'id' // Local key on users table
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* const country = await Country.find(1);
|
|
33
|
+
* const profile = await country.userProfile().get();
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @template TRelated - The type of the final related model
|
|
37
|
+
*/
|
|
38
|
+
export class HasOneThrough<TRelated extends Model> extends Relation<TRelated> {
|
|
39
|
+
/**
|
|
40
|
+
* The intermediate ("through") model instance.
|
|
41
|
+
*/
|
|
42
|
+
protected throughModel: Model;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The foreign key on the intermediate table (referencing parent).
|
|
46
|
+
* Example: 'country_id' on users table
|
|
47
|
+
*/
|
|
48
|
+
protected firstKey: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The foreign key on the final related table (referencing intermediate).
|
|
52
|
+
* Example: 'user_id' on profiles table
|
|
53
|
+
*/
|
|
54
|
+
protected secondKey: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The local key on the parent model.
|
|
58
|
+
* Example: 'id' on countries table
|
|
59
|
+
*/
|
|
60
|
+
protected localKey: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The local key on the intermediate model.
|
|
64
|
+
* Example: 'id' on users table
|
|
65
|
+
*/
|
|
66
|
+
protected secondLocalKey: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a new HasOneThrough relationship instance.
|
|
70
|
+
*
|
|
71
|
+
* @param parent - The parent model instance (e.g., Country)
|
|
72
|
+
* @param related - An instance of the final related model class (e.g., Profile)
|
|
73
|
+
* @param through - An instance of the intermediate model class (e.g., User)
|
|
74
|
+
* @param firstKey - Foreign key on intermediate table referencing parent (e.g., 'country_id')
|
|
75
|
+
* @param secondKey - Foreign key on final table referencing intermediate (e.g., 'user_id')
|
|
76
|
+
* @param localKey - Primary key on parent model (e.g., 'id')
|
|
77
|
+
* @param secondLocalKey - Primary key on intermediate model (e.g., 'id')
|
|
78
|
+
*/
|
|
79
|
+
constructor(
|
|
80
|
+
parent: Model,
|
|
81
|
+
related: TRelated,
|
|
82
|
+
through: Model,
|
|
83
|
+
firstKey: string,
|
|
84
|
+
secondKey: string,
|
|
85
|
+
localKey: string,
|
|
86
|
+
secondLocalKey: string
|
|
87
|
+
) {
|
|
88
|
+
super(parent, related);
|
|
89
|
+
|
|
90
|
+
this.throughModel = through;
|
|
91
|
+
this.firstKey = firstKey;
|
|
92
|
+
this.secondKey = secondKey;
|
|
93
|
+
this.localKey = localKey;
|
|
94
|
+
this.secondLocalKey = secondLocalKey;
|
|
95
|
+
|
|
96
|
+
// Re-add constraints now that keys are set
|
|
97
|
+
this.reinitializeConstraints();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Re-initialize constraints after keys are set.
|
|
102
|
+
*/
|
|
103
|
+
private reinitializeConstraints(): void {
|
|
104
|
+
this.query = this.newQuery();
|
|
105
|
+
const parentKeyValue = this.parent.getAttribute(this.localKey);
|
|
106
|
+
const throughTable = this.throughModel.getTable();
|
|
107
|
+
const relatedTable = this.related.getTable();
|
|
108
|
+
|
|
109
|
+
// Join the intermediate table to the final table
|
|
110
|
+
// profiles JOIN users ON profiles.user_id = users.id
|
|
111
|
+
this.query.join(
|
|
112
|
+
throughTable,
|
|
113
|
+
`${relatedTable}.${this.secondKey}`,
|
|
114
|
+
'=',
|
|
115
|
+
`${throughTable}.${this.secondLocalKey}`
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Constrain by parent's key through the intermediate table
|
|
119
|
+
// WHERE users.country_id = {country.id}
|
|
120
|
+
if (parentKeyValue !== undefined && parentKeyValue !== null) {
|
|
121
|
+
this.query.where(`${throughTable}.${this.firstKey}`, '=', parentKeyValue as Binding);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Add the constraints for the HasOneThrough relationship.
|
|
127
|
+
* Called by parent constructor - keys may not be set yet.
|
|
128
|
+
*/
|
|
129
|
+
public addConstraints(): void {
|
|
130
|
+
// Constraints are added in reinitializeConstraints after keys are set
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the results of the HasOneThrough relationship.
|
|
135
|
+
*
|
|
136
|
+
* Returns a single related model or null if the chain is broken.
|
|
137
|
+
*
|
|
138
|
+
* @returns The related model or null
|
|
139
|
+
*/
|
|
140
|
+
public async getResults(): Promise<TRelated | null> {
|
|
141
|
+
const parentKeyValue = this.parent.getAttribute(this.localKey);
|
|
142
|
+
|
|
143
|
+
// If parent doesn't have a key value, return null
|
|
144
|
+
if (parentKeyValue === undefined || parentKeyValue === null) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return this.first();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the intermediate model.
|
|
153
|
+
*
|
|
154
|
+
* @returns The through model instance
|
|
155
|
+
*/
|
|
156
|
+
public getThroughModel(): Model {
|
|
157
|
+
return this.throughModel;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get the first key (foreign key on intermediate table).
|
|
162
|
+
*
|
|
163
|
+
* @returns The first key column name
|
|
164
|
+
*/
|
|
165
|
+
public getFirstKeyName(): string {
|
|
166
|
+
return this.firstKey;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the second key (foreign key on final table).
|
|
171
|
+
*
|
|
172
|
+
* @returns The second key column name
|
|
173
|
+
*/
|
|
174
|
+
public getSecondKeyName(): string {
|
|
175
|
+
return this.secondKey;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the qualified first key name.
|
|
180
|
+
*
|
|
181
|
+
* @returns The qualified column name
|
|
182
|
+
*/
|
|
183
|
+
public getQualifiedFirstKeyName(): string {
|
|
184
|
+
return `${this.throughModel.getTable()}.${this.firstKey}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get the qualified second key name.
|
|
189
|
+
*
|
|
190
|
+
* @returns The qualified column name
|
|
191
|
+
*/
|
|
192
|
+
public getQualifiedSecondKeyName(): string {
|
|
193
|
+
return `${this.related.getTable()}.${this.secondKey}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ==========================================
|
|
197
|
+
// Eager Loading Methods
|
|
198
|
+
// ==========================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Initialize the relation for eager loading.
|
|
202
|
+
* Creates a fresh query with the through join but without parent-specific constraints.
|
|
203
|
+
*/
|
|
204
|
+
public override initializeForEagerLoading(): void {
|
|
205
|
+
this.query = this.newQuery();
|
|
206
|
+
const throughTable = this.throughModel.getTable();
|
|
207
|
+
const relatedTable = this.related.getTable();
|
|
208
|
+
|
|
209
|
+
// Explicitly select all columns from related table to preserve them when adding rawSelect
|
|
210
|
+
this.query.select(`${relatedTable}.*`);
|
|
211
|
+
|
|
212
|
+
// Join the intermediate table to the final table (required for HasOneThrough)
|
|
213
|
+
this.query.join(
|
|
214
|
+
throughTable,
|
|
215
|
+
`${relatedTable}.${this.secondKey}`,
|
|
216
|
+
'=',
|
|
217
|
+
`${throughTable}.${this.secondLocalKey}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Add constraints for eager loading.
|
|
223
|
+
* Uses WHERE IN on the through table to fetch all related models for multiple parents at once.
|
|
224
|
+
*
|
|
225
|
+
* @param models - Array of parent models to load relations for
|
|
226
|
+
*/
|
|
227
|
+
public addEagerConstraints(models: Model[]): void {
|
|
228
|
+
// Collect all parent key values
|
|
229
|
+
const keys: unknown[] = [];
|
|
230
|
+
for (const model of models) {
|
|
231
|
+
const key = model.getAttribute(this.localKey);
|
|
232
|
+
if (key !== undefined && key !== null) {
|
|
233
|
+
keys.push(key);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Remove duplicates
|
|
238
|
+
const uniqueKeys = [...new Set(keys)];
|
|
239
|
+
|
|
240
|
+
// Add WHERE IN constraint on the through table's first key
|
|
241
|
+
const throughTable = this.throughModel.getTable();
|
|
242
|
+
if (uniqueKeys.length > 0) {
|
|
243
|
+
this.query.whereIn(
|
|
244
|
+
`${throughTable}.${this.firstKey}`,
|
|
245
|
+
uniqueKeys as (string | number | boolean)[]
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Select the firstKey from through table to enable matching (use selectRaw for proper alias)
|
|
250
|
+
this.query.selectRaw(`${throughTable}.${this.firstKey} as laravel_through_key`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Match the eagerly loaded results to their parent models.
|
|
255
|
+
* For HasOneThrough, each parent gets at most one related model.
|
|
256
|
+
*
|
|
257
|
+
* @param models - Array of parent models
|
|
258
|
+
* @param results - Array of related models that were loaded
|
|
259
|
+
* @param relation - The name of the relationship being matched
|
|
260
|
+
*/
|
|
261
|
+
public match(models: Model[], results: TRelated[], relation: string): void {
|
|
262
|
+
// Build a dictionary for O(1) lookup: parent key value -> related model
|
|
263
|
+
// Use String conversion for consistent key comparison across types
|
|
264
|
+
const dictionary = new Map<string, TRelated>();
|
|
265
|
+
for (const result of results) {
|
|
266
|
+
// Get the through key from the result (added as laravel_through_key)
|
|
267
|
+
const throughKeyValue = String(result.getAttribute('laravel_through_key'));
|
|
268
|
+
// For HasOneThrough, only keep the first match (if duplicates exist)
|
|
269
|
+
if (!dictionary.has(throughKeyValue)) {
|
|
270
|
+
dictionary.set(throughKeyValue, result);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Match each parent model with its related model
|
|
275
|
+
for (const model of models) {
|
|
276
|
+
const localKeyValue = String(model.getAttribute(this.localKey));
|
|
277
|
+
const match = dictionary.get(localKeyValue) || null;
|
|
278
|
+
model.setRelation(relation, match);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|