create-phoenixjs 0.1.4 → 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.
Files changed (51) 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/20260108164611_TestCliMigration.ts +16 -0
  5. package/template/database/migrations/2026_01_08_16_46_11_CreateTestMigrationsTable.ts +21 -0
  6. package/template/framework/cli/artisan.ts +12 -0
  7. package/template/framework/database/DatabaseManager.ts +133 -0
  8. package/template/framework/database/connection/Connection.ts +71 -0
  9. package/template/framework/database/connection/ConnectionFactory.ts +30 -0
  10. package/template/framework/database/connection/PostgresConnection.ts +159 -0
  11. package/template/framework/database/console/MakeMigrationCommand.ts +58 -0
  12. package/template/framework/database/console/MigrateCommand.ts +32 -0
  13. package/template/framework/database/console/MigrateResetCommand.ts +31 -0
  14. package/template/framework/database/console/MigrateRollbackCommand.ts +31 -0
  15. package/template/framework/database/console/MigrateStatusCommand.ts +38 -0
  16. package/template/framework/database/migrations/DatabaseMigrationRepository.ts +122 -0
  17. package/template/framework/database/migrations/Migration.ts +5 -0
  18. package/template/framework/database/migrations/MigrationRepository.ts +46 -0
  19. package/template/framework/database/migrations/Migrator.ts +249 -0
  20. package/template/framework/database/migrations/index.ts +4 -0
  21. package/template/framework/database/orm/BelongsTo.ts +246 -0
  22. package/template/framework/database/orm/BelongsToMany.ts +570 -0
  23. package/template/framework/database/orm/Builder.ts +160 -0
  24. package/template/framework/database/orm/EagerLoadingBuilder.ts +324 -0
  25. package/template/framework/database/orm/HasMany.ts +303 -0
  26. package/template/framework/database/orm/HasManyThrough.ts +282 -0
  27. package/template/framework/database/orm/HasOne.ts +201 -0
  28. package/template/framework/database/orm/HasOneThrough.ts +281 -0
  29. package/template/framework/database/orm/Model.ts +1766 -0
  30. package/template/framework/database/orm/Relation.ts +342 -0
  31. package/template/framework/database/orm/Scope.ts +14 -0
  32. package/template/framework/database/orm/SoftDeletes.ts +160 -0
  33. package/template/framework/database/orm/index.ts +54 -0
  34. package/template/framework/database/orm/scopes/SoftDeletingScope.ts +58 -0
  35. package/template/framework/database/pagination/LengthAwarePaginator.ts +55 -0
  36. package/template/framework/database/pagination/Paginator.ts +110 -0
  37. package/template/framework/database/pagination/index.ts +2 -0
  38. package/template/framework/database/query/Builder.ts +918 -0
  39. package/template/framework/database/query/DB.ts +139 -0
  40. package/template/framework/database/query/grammars/Grammar.ts +430 -0
  41. package/template/framework/database/query/grammars/PostgresGrammar.ts +224 -0
  42. package/template/framework/database/query/grammars/index.ts +6 -0
  43. package/template/framework/database/query/index.ts +8 -0
  44. package/template/framework/database/query/types.ts +196 -0
  45. package/template/framework/database/schema/Blueprint.ts +478 -0
  46. package/template/framework/database/schema/Schema.ts +149 -0
  47. package/template/framework/database/schema/SchemaBuilder.ts +152 -0
  48. package/template/framework/database/schema/grammars/PostgresSchemaGrammar.ts +293 -0
  49. package/template/framework/database/schema/grammars/index.ts +5 -0
  50. package/template/framework/database/schema/index.ts +9 -0
  51. package/template/package.json +4 -1
@@ -0,0 +1,152 @@
1
+ /**
2
+ * PhoenixJS ORM - Schema Builder
3
+ *
4
+ * Executes schema operations using a database connection and grammar.
5
+ */
6
+
7
+ import type { Connection } from '../connection/Connection';
8
+ import { Blueprint } from './Blueprint';
9
+ import { PostgresSchemaGrammar } from './grammars/PostgresSchemaGrammar';
10
+
11
+ /**
12
+ * Schema Builder - executes DDL operations
13
+ */
14
+ export class SchemaBuilder {
15
+ private grammar: PostgresSchemaGrammar;
16
+
17
+ constructor(
18
+ private connection: Connection,
19
+ grammar?: PostgresSchemaGrammar
20
+ ) {
21
+ this.grammar = grammar || new PostgresSchemaGrammar();
22
+ }
23
+
24
+ /**
25
+ * Create a new table with the given callback
26
+ */
27
+ async create(table: string, callback: (blueprint: Blueprint) => void): Promise<void> {
28
+ const blueprint = new Blueprint(table);
29
+ callback(blueprint);
30
+
31
+ const sql = this.grammar.compileCreate(blueprint);
32
+ await this.connection.execute(sql);
33
+
34
+ // Create indexes separately
35
+ const indexes = blueprint.getIndexes();
36
+ for (const index of indexes) {
37
+ const indexSql = this.grammar.compileCreateIndex(table, index);
38
+ await this.connection.execute(indexSql);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Modify an existing table
44
+ */
45
+ async table(table: string, callback: (blueprint: Blueprint) => void): Promise<void> {
46
+ const blueprint = new Blueprint(table);
47
+ callback(blueprint);
48
+
49
+ const statements = this.grammar.compileAlter(blueprint);
50
+ for (const sql of statements) {
51
+ await this.connection.execute(sql);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Drop a table
57
+ */
58
+ async drop(table: string): Promise<void> {
59
+ const sql = this.grammar.compileDrop(table);
60
+ await this.connection.execute(sql);
61
+ }
62
+
63
+ /**
64
+ * Drop a table if it exists
65
+ */
66
+ async dropIfExists(table: string): Promise<void> {
67
+ const sql = this.grammar.compileDropIfExists(table);
68
+ await this.connection.execute(sql);
69
+ }
70
+
71
+ /**
72
+ * Rename a table
73
+ */
74
+ async rename(from: string, to: string): Promise<void> {
75
+ const sql = this.grammar.compileRename(from, to);
76
+ await this.connection.execute(sql);
77
+ }
78
+
79
+ /**
80
+ * Check if a table exists
81
+ */
82
+ async hasTable(table: string): Promise<boolean> {
83
+ const sql = this.grammar.compileTableExists(table);
84
+ const result = await this.connection.query<{ exists: boolean }>(sql);
85
+ return result[0]?.exists ?? false;
86
+ }
87
+
88
+ /**
89
+ * Check if a column exists in a table
90
+ */
91
+ async hasColumn(table: string, column: string): Promise<boolean> {
92
+ const sql = this.grammar.compileColumnExists(table, column);
93
+ const result = await this.connection.query<{ exists: boolean }>(sql);
94
+ return result[0]?.exists ?? false;
95
+ }
96
+
97
+ /**
98
+ * Get all tables in the database
99
+ */
100
+ async getTables(): Promise<string[]> {
101
+ const sql = this.grammar.compileGetTables();
102
+ const result = await this.connection.query<{ table_name: string }>(sql);
103
+ return result.map((row) => row.table_name);
104
+ }
105
+
106
+ /**
107
+ * Get all columns for a table
108
+ */
109
+ async getColumns(table: string): Promise<
110
+ {
111
+ column_name: string;
112
+ data_type: string;
113
+ is_nullable: string;
114
+ column_default: string | null;
115
+ }[]
116
+ > {
117
+ const sql = this.grammar.compileGetColumns(table);
118
+ return this.connection.query(sql);
119
+ }
120
+
121
+ /**
122
+ * Truncate a table
123
+ */
124
+ async truncate(table: string): Promise<void> {
125
+ const sql = this.grammar.compileTruncate(table);
126
+ await this.connection.execute(sql);
127
+ }
128
+
129
+ /**
130
+ * Drop all tables in the database (dangerous!)
131
+ */
132
+ async dropAllTables(): Promise<void> {
133
+ const tables = await this.getTables();
134
+ for (const table of tables) {
135
+ await this.dropIfExists(table);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Get the grammar instance
141
+ */
142
+ getGrammar(): PostgresSchemaGrammar {
143
+ return this.grammar;
144
+ }
145
+
146
+ /**
147
+ * Get the connection instance
148
+ */
149
+ getConnection(): Connection {
150
+ return this.connection;
151
+ }
152
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * PhoenixJS ORM - PostgreSQL Schema Grammar
3
+ *
4
+ * Compiles Blueprint definitions into PostgreSQL DDL statements.
5
+ */
6
+
7
+ import type {
8
+ Blueprint,
9
+ ColumnDefinition,
10
+ IndexDefinition,
11
+ ForeignKeyDefinition,
12
+ } from '../Blueprint';
13
+
14
+ /**
15
+ * PostgreSQL Schema Grammar
16
+ *
17
+ * Compiles Blueprint column definitions into PostgreSQL DDL statements.
18
+ */
19
+ export class PostgresSchemaGrammar {
20
+ /**
21
+ * Wrap an identifier in quotes
22
+ */
23
+ wrap(value: string): string {
24
+ if (value === '*') return value;
25
+ return `"${value}"`;
26
+ }
27
+
28
+ /**
29
+ * Wrap a table name
30
+ */
31
+ wrapTable(table: string): string {
32
+ return this.wrap(table);
33
+ }
34
+
35
+ /**
36
+ * Compile a CREATE TABLE statement
37
+ */
38
+ compileCreate(blueprint: Blueprint): string {
39
+ const tableName = this.wrapTable(blueprint.getTableName());
40
+ const columns = blueprint.getColumns();
41
+
42
+ if (columns.length === 0) {
43
+ throw new Error('Cannot create table without columns');
44
+ }
45
+
46
+ const columnDefinitions = columns.map((col) => this.compileColumn(col));
47
+
48
+ // Add primary key constraints for non-serial columns
49
+ const primaryColumns = columns.filter((c) => c.primary && !c.autoIncrement);
50
+ if (primaryColumns.length > 0) {
51
+ const pkColumns = primaryColumns.map((c) => this.wrap(c.name)).join(', ');
52
+ columnDefinitions.push(`PRIMARY KEY (${pkColumns})`);
53
+ }
54
+
55
+ // Add unique constraints for unique columns
56
+ const uniqueColumns = columns.filter((c) => c.unique);
57
+ for (const col of uniqueColumns) {
58
+ columnDefinitions.push(`UNIQUE (${this.wrap(col.name)})`);
59
+ }
60
+
61
+ // Add foreign key constraints
62
+ const foreignKeys = blueprint.getForeignKeys();
63
+ for (const fk of foreignKeys) {
64
+ columnDefinitions.push(this.compileForeignKey(fk));
65
+ }
66
+
67
+ return `CREATE TABLE ${tableName} (\n ${columnDefinitions.join(',\n ')}\n)`;
68
+ }
69
+
70
+ /**
71
+ * Compile a single column definition
72
+ */
73
+ compileColumn(column: ColumnDefinition): string {
74
+ const parts: string[] = [this.wrap(column.name)];
75
+
76
+ // Type with length/precision
77
+ parts.push(this.getColumnType(column));
78
+
79
+ // Primary key for SERIAL types
80
+ if (column.primary && column.autoIncrement) {
81
+ parts.push('PRIMARY KEY');
82
+ }
83
+
84
+ // Nullable
85
+ if (!column.nullable && !column.autoIncrement) {
86
+ parts.push('NOT NULL');
87
+ }
88
+
89
+ // Default value
90
+ if (column.defaultValue !== undefined) {
91
+ parts.push(`DEFAULT ${this.compileDefaultValue(column.defaultValue)}`);
92
+ }
93
+
94
+ // Foreign key reference (inline)
95
+ if (column.references && column.references.table) {
96
+ parts.push(
97
+ `REFERENCES ${this.wrap(column.references.table)}(${this.wrap(column.references.column)})`
98
+ );
99
+ if (column.references.onDelete) {
100
+ parts.push(`ON DELETE ${column.references.onDelete}`);
101
+ }
102
+ if (column.references.onUpdate) {
103
+ parts.push(`ON UPDATE ${column.references.onUpdate}`);
104
+ }
105
+ }
106
+
107
+ return parts.join(' ');
108
+ }
109
+
110
+ /**
111
+ * Get the PostgreSQL column type
112
+ */
113
+ private getColumnType(column: ColumnDefinition): string {
114
+ switch (column.type) {
115
+ case 'VARCHAR':
116
+ return `VARCHAR(${column.length || 255})`;
117
+ case 'CHAR':
118
+ return `CHAR(${column.length || 255})`;
119
+ case 'NUMERIC':
120
+ return `NUMERIC(${column.precision || 8}, ${column.scale || 2})`;
121
+ case 'REAL':
122
+ return column.precision ? `FLOAT(${column.precision})` : 'REAL';
123
+ case 'TIMESTAMPTZ':
124
+ return 'TIMESTAMP WITH TIME ZONE';
125
+ default:
126
+ return column.type;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Compile a default value
132
+ */
133
+ private compileDefaultValue(value: string | number | boolean | null): string {
134
+ if (value === null) return 'NULL';
135
+ if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE';
136
+ if (typeof value === 'number') return String(value);
137
+ // Check for SQL expressions like CURRENT_TIMESTAMP
138
+ if (
139
+ value === 'CURRENT_TIMESTAMP' ||
140
+ value === 'NOW()' ||
141
+ value.startsWith('gen_random_uuid')
142
+ ) {
143
+ return value;
144
+ }
145
+ return `'${value}'`;
146
+ }
147
+
148
+ /**
149
+ * Compile a foreign key constraint
150
+ */
151
+ private compileForeignKey(fk: ForeignKeyDefinition): string {
152
+ const columns = fk.columns.map((c) => this.wrap(c)).join(', ');
153
+ const refs = fk.references.map((c) => this.wrap(c)).join(', ');
154
+
155
+ let sql = `FOREIGN KEY (${columns}) REFERENCES ${this.wrap(fk.on)}(${refs})`;
156
+
157
+ if (fk.onDelete) {
158
+ sql += ` ON DELETE ${fk.onDelete}`;
159
+ }
160
+ if (fk.onUpdate) {
161
+ sql += ` ON UPDATE ${fk.onUpdate}`;
162
+ }
163
+
164
+ return sql;
165
+ }
166
+
167
+ /**
168
+ * Compile an ALTER TABLE ADD COLUMN statement
169
+ */
170
+ compileAdd(table: string, column: ColumnDefinition): string {
171
+ return `ALTER TABLE ${this.wrapTable(table)} ADD COLUMN ${this.compileColumn(column)}`;
172
+ }
173
+
174
+ /**
175
+ * Compile ALTER TABLE statements for modifying a table
176
+ */
177
+ compileAlter(blueprint: Blueprint): string[] {
178
+ const statements: string[] = [];
179
+ const table = this.wrapTable(blueprint.getTableName());
180
+
181
+ // Add new columns
182
+ for (const column of blueprint.getColumns()) {
183
+ statements.push(`ALTER TABLE ${table} ADD COLUMN ${this.compileColumn(column)}`);
184
+ }
185
+
186
+ // Drop columns
187
+ for (const column of blueprint.getDroppedColumns()) {
188
+ statements.push(`ALTER TABLE ${table} DROP COLUMN IF EXISTS ${this.wrap(column)}`);
189
+ }
190
+
191
+ // Add indexes
192
+ for (const index of blueprint.getIndexes()) {
193
+ statements.push(this.compileCreateIndex(blueprint.getTableName(), index));
194
+ }
195
+
196
+ // Drop indexes
197
+ for (const indexName of blueprint.getDroppedIndexes()) {
198
+ statements.push(`DROP INDEX IF EXISTS ${this.wrap(indexName)}`);
199
+ }
200
+
201
+ // Add foreign keys
202
+ for (const fk of blueprint.getForeignKeys()) {
203
+ statements.push(`ALTER TABLE ${table} ADD ${this.compileForeignKey(fk)}`);
204
+ }
205
+
206
+ return statements;
207
+ }
208
+
209
+ /**
210
+ * Compile a DROP TABLE statement
211
+ */
212
+ compileDrop(table: string): string {
213
+ return `DROP TABLE ${this.wrapTable(table)}`;
214
+ }
215
+
216
+ /**
217
+ * Compile a DROP TABLE IF EXISTS statement
218
+ */
219
+ compileDropIfExists(table: string): string {
220
+ return `DROP TABLE IF EXISTS ${this.wrapTable(table)} CASCADE`;
221
+ }
222
+
223
+ /**
224
+ * Compile a RENAME TABLE statement
225
+ */
226
+ compileRename(from: string, to: string): string {
227
+ return `ALTER TABLE ${this.wrapTable(from)} RENAME TO ${this.wrap(to)}`;
228
+ }
229
+
230
+ /**
231
+ * Compile a CREATE INDEX statement
232
+ */
233
+ compileCreateIndex(table: string, index: IndexDefinition): string {
234
+ const unique = index.unique ? 'UNIQUE ' : '';
235
+ const columns = index.columns.map((c) => this.wrap(c)).join(', ');
236
+ return `CREATE ${unique}INDEX ${this.wrap(index.name)} ON ${this.wrapTable(table)} (${columns})`;
237
+ }
238
+
239
+ /**
240
+ * Compile a DROP INDEX statement
241
+ */
242
+ compileDropIndex(name: string): string {
243
+ return `DROP INDEX IF EXISTS ${this.wrap(name)}`;
244
+ }
245
+
246
+ /**
247
+ * Compile a check table exists query
248
+ */
249
+ compileTableExists(table: string): string {
250
+ return `SELECT EXISTS (
251
+ SELECT FROM information_schema.tables
252
+ WHERE table_schema = 'public'
253
+ AND table_name = '${table}'
254
+ )`;
255
+ }
256
+
257
+ /**
258
+ * Compile a check column exists query
259
+ */
260
+ compileColumnExists(table: string, column: string): string {
261
+ return `SELECT EXISTS (
262
+ SELECT FROM information_schema.columns
263
+ WHERE table_schema = 'public'
264
+ AND table_name = '${table}'
265
+ AND column_name = '${column}'
266
+ )`;
267
+ }
268
+
269
+ /**
270
+ * Compile get all tables query
271
+ */
272
+ compileGetTables(): string {
273
+ return `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name`;
274
+ }
275
+
276
+ /**
277
+ * Compile get all columns for a table query
278
+ */
279
+ compileGetColumns(table: string): string {
280
+ return `SELECT column_name, data_type, is_nullable, column_default
281
+ FROM information_schema.columns
282
+ WHERE table_schema = 'public'
283
+ AND table_name = '${table}'
284
+ ORDER BY ordinal_position`;
285
+ }
286
+
287
+ /**
288
+ * Compile a TRUNCATE TABLE statement
289
+ */
290
+ compileTruncate(table: string): string {
291
+ return `TRUNCATE TABLE ${this.wrapTable(table)} RESTART IDENTITY CASCADE`;
292
+ }
293
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * PhoenixJS ORM - Schema Grammar Exports
3
+ */
4
+
5
+ export { PostgresSchemaGrammar } from './PostgresSchemaGrammar';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * PhoenixJS ORM - Schema Module Exports
3
+ */
4
+
5
+ export { Blueprint, ColumnBuilder } from './Blueprint';
6
+ export type { ColumnDefinition, IndexDefinition, ForeignKeyDefinition } from './Blueprint';
7
+ export { SchemaBuilder } from './SchemaBuilder';
8
+ export { Schema } from './Schema';
9
+ export { PostgresSchemaGrammar } from './grammars/PostgresSchemaGrammar';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-phoenixjs-app",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "A PhoenixJS API project",
5
5
  "type": "module",
6
6
  "main": "server.ts",
@@ -20,5 +20,8 @@
20
20
  "devDependencies": {
21
21
  "@types/bun": "latest",
22
22
  "typescript": "^5.0.0"
23
+ },
24
+ "dependencies": {
25
+ "postgres": "3.4.8"
23
26
  }
24
27
  }