create-phoenixjs 0.1.4 → 0.1.6

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.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/template/config/database.ts +13 -1
  3. package/template/database/migrations/2024_01_01_000000_create_test_users_cli_table.ts +16 -0
  4. package/template/database/migrations/20260108165704_TestCliMigration.ts +16 -0
  5. package/template/database/migrations/2026_01_08_16_57_04_CreateTestMigrationsTable.ts +21 -0
  6. package/template/framework/cli/artisan.ts +12 -0
  7. package/template/framework/cli/commands/MakeModelCommand.ts +33 -3
  8. package/template/framework/database/DatabaseManager.ts +133 -0
  9. package/template/framework/database/connection/Connection.ts +71 -0
  10. package/template/framework/database/connection/ConnectionFactory.ts +30 -0
  11. package/template/framework/database/connection/PostgresConnection.ts +159 -0
  12. package/template/framework/database/console/MakeMigrationCommand.ts +58 -0
  13. package/template/framework/database/console/MigrateCommand.ts +32 -0
  14. package/template/framework/database/console/MigrateResetCommand.ts +31 -0
  15. package/template/framework/database/console/MigrateRollbackCommand.ts +31 -0
  16. package/template/framework/database/console/MigrateStatusCommand.ts +38 -0
  17. package/template/framework/database/migrations/DatabaseMigrationRepository.ts +122 -0
  18. package/template/framework/database/migrations/Migration.ts +5 -0
  19. package/template/framework/database/migrations/MigrationRepository.ts +46 -0
  20. package/template/framework/database/migrations/Migrator.ts +249 -0
  21. package/template/framework/database/migrations/index.ts +4 -0
  22. package/template/framework/database/orm/BelongsTo.ts +246 -0
  23. package/template/framework/database/orm/BelongsToMany.ts +570 -0
  24. package/template/framework/database/orm/Builder.ts +160 -0
  25. package/template/framework/database/orm/EagerLoadingBuilder.ts +324 -0
  26. package/template/framework/database/orm/HasMany.ts +303 -0
  27. package/template/framework/database/orm/HasManyThrough.ts +282 -0
  28. package/template/framework/database/orm/HasOne.ts +201 -0
  29. package/template/framework/database/orm/HasOneThrough.ts +281 -0
  30. package/template/framework/database/orm/Model.ts +1802 -0
  31. package/template/framework/database/orm/Relation.ts +342 -0
  32. package/template/framework/database/orm/Scope.ts +14 -0
  33. package/template/framework/database/orm/SoftDeletes.ts +160 -0
  34. package/template/framework/database/orm/index.ts +54 -0
  35. package/template/framework/database/orm/scopes/SoftDeletingScope.ts +58 -0
  36. package/template/framework/database/pagination/LengthAwarePaginator.ts +55 -0
  37. package/template/framework/database/pagination/Paginator.ts +110 -0
  38. package/template/framework/database/pagination/index.ts +2 -0
  39. package/template/framework/database/query/Builder.ts +918 -0
  40. package/template/framework/database/query/DB.ts +139 -0
  41. package/template/framework/database/query/grammars/Grammar.ts +430 -0
  42. package/template/framework/database/query/grammars/PostgresGrammar.ts +224 -0
  43. package/template/framework/database/query/grammars/index.ts +6 -0
  44. package/template/framework/database/query/index.ts +8 -0
  45. package/template/framework/database/query/types.ts +196 -0
  46. package/template/framework/database/schema/Blueprint.ts +478 -0
  47. package/template/framework/database/schema/Schema.ts +149 -0
  48. package/template/framework/database/schema/SchemaBuilder.ts +152 -0
  49. package/template/framework/database/schema/grammars/PostgresSchemaGrammar.ts +293 -0
  50. package/template/framework/database/schema/grammars/index.ts +5 -0
  51. package/template/framework/database/schema/index.ts +9 -0
  52. package/template/package.json +4 -1
@@ -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
+ }