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,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,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';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS - Logger
|
|
3
|
+
*
|
|
4
|
+
* A NestJS-inspired logger service with color support, timestamps, and context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose';
|
|
8
|
+
|
|
9
|
+
export class Logger {
|
|
10
|
+
private static instance: Logger;
|
|
11
|
+
private context?: string;
|
|
12
|
+
private static isDebugMode = process.env.NODE_ENV !== 'production';
|
|
13
|
+
private static isEnabled = true;
|
|
14
|
+
|
|
15
|
+
// ANSI Colors
|
|
16
|
+
private static readonly COLORS = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
bright: '\x1b[1m',
|
|
19
|
+
dim: '\x1b[2m',
|
|
20
|
+
underscore: '\x1b[4m',
|
|
21
|
+
blink: '\x1b[5m',
|
|
22
|
+
reverse: '\x1b[7m',
|
|
23
|
+
hidden: '\x1b[8m',
|
|
24
|
+
|
|
25
|
+
fgBlack: '\x1b[30m',
|
|
26
|
+
fgRed: '\x1b[31m',
|
|
27
|
+
fgGreen: '\x1b[32m',
|
|
28
|
+
fgYellow: '\x1b[33m',
|
|
29
|
+
fgBlue: '\x1b[34m',
|
|
30
|
+
fgMagenta: '\x1b[35m',
|
|
31
|
+
fgCyan: '\x1b[36m',
|
|
32
|
+
fgWhite: '\x1b[37m',
|
|
33
|
+
|
|
34
|
+
bgBlack: '\x1b[40m',
|
|
35
|
+
bgRed: '\x1b[41m',
|
|
36
|
+
bgGreen: '\x1b[42m',
|
|
37
|
+
bgYellow: '\x1b[43m',
|
|
38
|
+
bgBlue: '\x1b[44m',
|
|
39
|
+
bgMagenta: '\x1b[45m',
|
|
40
|
+
bgCyan: '\x1b[46m',
|
|
41
|
+
bgWhite: '\x1b[47m',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
constructor(context?: string) {
|
|
45
|
+
this.context = context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configure the logger
|
|
50
|
+
*/
|
|
51
|
+
static configure(options: { enabled?: boolean }): void {
|
|
52
|
+
if (options.enabled !== undefined) {
|
|
53
|
+
Logger.isEnabled = options.enabled;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the logger context
|
|
59
|
+
*/
|
|
60
|
+
setContext(context: string): void {
|
|
61
|
+
this.context = context;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Log a message (Level: LOG)
|
|
66
|
+
*/
|
|
67
|
+
log(message: any, context?: string): void {
|
|
68
|
+
if (!Logger.isEnabled) return;
|
|
69
|
+
this.printMessage('log', message, context || this.context);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Log an error message (Level: ERROR)
|
|
74
|
+
*/
|
|
75
|
+
error(message: any, trace?: string, context?: string): void {
|
|
76
|
+
if (!Logger.isEnabled) return;
|
|
77
|
+
this.printMessage('error', message, context || this.context, trace);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Log a warning message (Level: WARN)
|
|
82
|
+
*/
|
|
83
|
+
warn(message: any, context?: string): void {
|
|
84
|
+
if (!Logger.isEnabled) return;
|
|
85
|
+
this.printMessage('warn', message, context || this.context);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Log a debug message (Level: DEBUG)
|
|
90
|
+
*/
|
|
91
|
+
debug(message: any, context?: string): void {
|
|
92
|
+
if (!Logger.isEnabled || !Logger.isDebugMode) return;
|
|
93
|
+
this.printMessage('debug', message, context || this.context);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Log a verbose message (Level: VERBOSE)
|
|
98
|
+
*/
|
|
99
|
+
verbose(message: any, context?: string): void {
|
|
100
|
+
if (!Logger.isEnabled || !Logger.isDebugMode) return;
|
|
101
|
+
this.printMessage('verbose', message, context || this.context);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Static method to log a message
|
|
106
|
+
*/
|
|
107
|
+
static log(message: any, context?: string): void {
|
|
108
|
+
new Logger(context).log(message);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Static method to log an error
|
|
113
|
+
*/
|
|
114
|
+
static error(message: any, trace?: string, context?: string): void {
|
|
115
|
+
new Logger(context).error(message, trace);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Static method to log a warning
|
|
120
|
+
*/
|
|
121
|
+
static warn(message: any, context?: string): void {
|
|
122
|
+
new Logger(context).warn(message);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Static method to log debug
|
|
127
|
+
*/
|
|
128
|
+
static debug(message: any, context?: string): void {
|
|
129
|
+
new Logger(context).debug(message);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Static method to log verbose
|
|
134
|
+
*/
|
|
135
|
+
static verbose(message: any, context?: string): void {
|
|
136
|
+
new Logger(context).verbose(message);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Print the log message with formatting
|
|
141
|
+
*/
|
|
142
|
+
private printMessage(level: LogLevel, message: any, context: string = 'System', trace?: string): void {
|
|
143
|
+
const timestamp = new Date().toLocaleString();
|
|
144
|
+
const pid = process.pid;
|
|
145
|
+
const color = this.getColor(level);
|
|
146
|
+
const reset = Logger.COLORS.reset;
|
|
147
|
+
const bright = Logger.COLORS.bright;
|
|
148
|
+
const dim = Logger.COLORS.dim;
|
|
149
|
+
const yellow = Logger.COLORS.fgYellow;
|
|
150
|
+
|
|
151
|
+
// Format: [AppName] PID - Timestamp LEVEL [Context] Message
|
|
152
|
+
// Example: [PhoenixJS] 1234 - 1/5/2026, 10:00:00 AM LOG [Router] Route registered
|
|
153
|
+
|
|
154
|
+
const appName = `[PhoenixJS]`;
|
|
155
|
+
const pidStr = `${pid}`;
|
|
156
|
+
const timeStr = timestamp;
|
|
157
|
+
const levelStr = level.toUpperCase().padEnd(7);
|
|
158
|
+
const contextStr = `[${context}]`;
|
|
159
|
+
|
|
160
|
+
let output = '';
|
|
161
|
+
|
|
162
|
+
// Green AppName
|
|
163
|
+
output += `${Logger.COLORS.fgGreen}${appName}${reset} `;
|
|
164
|
+
// Yellow PID
|
|
165
|
+
output += `${yellow}${pidStr}${reset} - `;
|
|
166
|
+
// White Timestamp
|
|
167
|
+
output += `${timeStr} `;
|
|
168
|
+
// Configured Color Level
|
|
169
|
+
output += `${color}${levelStr}${reset} `;
|
|
170
|
+
// Yellow Context
|
|
171
|
+
output += `${yellow}${contextStr}${reset} `;
|
|
172
|
+
// Message
|
|
173
|
+
output += `${color}${typeof message === 'object' ? JSON.stringify(message, null, 2) : message}${reset}`;
|
|
174
|
+
|
|
175
|
+
if (trace) {
|
|
176
|
+
output += `\n${Logger.COLORS.fgRed}${trace}${reset}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(output);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get ANSI color for log level
|
|
184
|
+
*/
|
|
185
|
+
private getColor(level: LogLevel): string {
|
|
186
|
+
switch (level) {
|
|
187
|
+
case 'log': return Logger.COLORS.fgGreen;
|
|
188
|
+
case 'error': return Logger.COLORS.fgRed;
|
|
189
|
+
case 'warn': return Logger.COLORS.fgYellow;
|
|
190
|
+
case 'debug': return Logger.COLORS.fgMagenta;
|
|
191
|
+
case 'verbose': return Logger.COLORS.fgCyan;
|
|
192
|
+
default: return Logger.COLORS.fgWhite;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
package/template/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-phoenixjs-app",
|
|
3
|
-
"version": "0.1.
|
|
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
|
}
|