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,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - PostgreSQL Grammar
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL-specific grammar implementation for query compilation.
|
|
5
|
+
* Handles PostgreSQL parameter binding syntax ($1, $2, $3...) and
|
|
6
|
+
* PostgreSQL-specific SQL generation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Grammar } from './Grammar';
|
|
10
|
+
import type {
|
|
11
|
+
QueryComponents,
|
|
12
|
+
CompiledQuery,
|
|
13
|
+
InsertValues,
|
|
14
|
+
UpdateValues,
|
|
15
|
+
Binding,
|
|
16
|
+
WhereClause,
|
|
17
|
+
} from '../types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* PostgreSQL Grammar implementation
|
|
21
|
+
*
|
|
22
|
+
* Compiles query builder representations into PostgreSQL-compatible SQL.
|
|
23
|
+
*/
|
|
24
|
+
export class PostgresGrammar extends Grammar {
|
|
25
|
+
/**
|
|
26
|
+
* Wrap a value in PostgreSQL keyword identifiers (double quotes)
|
|
27
|
+
*/
|
|
28
|
+
wrap(value: string): string {
|
|
29
|
+
// Handle * without wrapping
|
|
30
|
+
if (value === '*') {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle table.column format
|
|
35
|
+
if (value.includes('.')) {
|
|
36
|
+
return value
|
|
37
|
+
.split('.')
|
|
38
|
+
.map((segment) => (segment === '*' ? '*' : `"${segment}"`))
|
|
39
|
+
.join('.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle column aliases (column AS alias)
|
|
43
|
+
if (value.toLowerCase().includes(' as ')) {
|
|
44
|
+
const parts = value.split(/\s+as\s+/i);
|
|
45
|
+
return `${this.wrap(parts[0])} AS ${this.wrap(parts[1])}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle raw expressions wrapped in parentheses
|
|
49
|
+
if (value.startsWith('(') && value.endsWith(')')) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return `"${value}"`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the grammar's parameter prefix
|
|
58
|
+
*/
|
|
59
|
+
getParameterPrefix(): string {
|
|
60
|
+
return '$';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create query parameter placeholders for PostgreSQL ($1, $2, $3...)
|
|
65
|
+
*/
|
|
66
|
+
parameterize(values: Binding[], startIndex: number = 1): string {
|
|
67
|
+
return values.map((_, index) => `$${startIndex + index}`).join(', ');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compile a basic where clause
|
|
72
|
+
*/
|
|
73
|
+
protected compileBasicWhere(where: WhereClause, bindings: Binding[]): string {
|
|
74
|
+
const column = this.wrap(where.column!);
|
|
75
|
+
bindings.push(where.value as Binding);
|
|
76
|
+
const paramIndex = bindings.length;
|
|
77
|
+
return `${column} ${where.operator} $${paramIndex}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compile a where in clause
|
|
82
|
+
*/
|
|
83
|
+
protected compileInWhere(where: WhereClause, bindings: Binding[]): string {
|
|
84
|
+
const column = this.wrap(where.column!);
|
|
85
|
+
const values = where.value as Binding[];
|
|
86
|
+
const startIndex = bindings.length + 1;
|
|
87
|
+
|
|
88
|
+
values.forEach((val) => bindings.push(val));
|
|
89
|
+
|
|
90
|
+
const placeholders = values.map((_, i) => `$${startIndex + i}`).join(', ');
|
|
91
|
+
const operator = where.not ? 'NOT IN' : 'IN';
|
|
92
|
+
|
|
93
|
+
return `${column} ${operator} (${placeholders})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Compile a where between clause
|
|
98
|
+
*/
|
|
99
|
+
protected compileBetweenWhere(where: WhereClause, bindings: Binding[]): string {
|
|
100
|
+
const column = this.wrap(where.column!);
|
|
101
|
+
const values = where.values!;
|
|
102
|
+
|
|
103
|
+
bindings.push(values[0], values[1]);
|
|
104
|
+
const param1 = bindings.length - 1;
|
|
105
|
+
const param2 = bindings.length;
|
|
106
|
+
|
|
107
|
+
const operator = where.not ? 'NOT BETWEEN' : 'BETWEEN';
|
|
108
|
+
return `${column} ${operator} $${param1} AND $${param2}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Compile an INSERT statement
|
|
113
|
+
*/
|
|
114
|
+
compileInsert(query: QueryComponents, values: InsertValues | InsertValues[]): CompiledQuery {
|
|
115
|
+
const table = this.wrapTable(query.table);
|
|
116
|
+
const valueArray = Array.isArray(values) ? values : [values];
|
|
117
|
+
|
|
118
|
+
if (valueArray.length === 0) {
|
|
119
|
+
return { sql: `INSERT INTO ${table} DEFAULT VALUES`, bindings: [] };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const columns = Object.keys(valueArray[0]);
|
|
123
|
+
const columnList = this.columnize(columns);
|
|
124
|
+
|
|
125
|
+
const bindings: Binding[] = [];
|
|
126
|
+
const valueSets = valueArray.map((row) => {
|
|
127
|
+
const placeholders = columns.map((col) => {
|
|
128
|
+
bindings.push(row[col]);
|
|
129
|
+
return `$${bindings.length}`;
|
|
130
|
+
});
|
|
131
|
+
return `(${placeholders.join(', ')})`;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const sql = `INSERT INTO ${table} (${columnList}) VALUES ${valueSets.join(', ')}`;
|
|
135
|
+
return { sql, bindings };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compile an INSERT statement with RETURNING clause
|
|
140
|
+
*/
|
|
141
|
+
compileInsertReturning(
|
|
142
|
+
query: QueryComponents,
|
|
143
|
+
values: InsertValues | InsertValues[],
|
|
144
|
+
returning: string[]
|
|
145
|
+
): CompiledQuery {
|
|
146
|
+
const { sql, bindings } = this.compileInsert(query, values);
|
|
147
|
+
const returningColumns = returning.length === 0 ? '*' : this.columnize(returning);
|
|
148
|
+
return { sql: `${sql} RETURNING ${returningColumns}`, bindings };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Compile an UPDATE statement
|
|
153
|
+
*/
|
|
154
|
+
compileUpdate(query: QueryComponents, values: UpdateValues): CompiledQuery {
|
|
155
|
+
const table = this.wrapTable(query.table);
|
|
156
|
+
const bindings: Binding[] = [];
|
|
157
|
+
|
|
158
|
+
// Compile SET clause
|
|
159
|
+
const setClause = Object.entries(values)
|
|
160
|
+
.map(([column, value]) => {
|
|
161
|
+
bindings.push(value);
|
|
162
|
+
return `${this.wrap(column)} = $${bindings.length}`;
|
|
163
|
+
})
|
|
164
|
+
.join(', ');
|
|
165
|
+
|
|
166
|
+
// Compile WHERE clause
|
|
167
|
+
const whereClause = this.compileWheres(query, bindings);
|
|
168
|
+
|
|
169
|
+
const sql = `UPDATE ${table} SET ${setClause}${whereClause ? ` ${whereClause}` : ''}`;
|
|
170
|
+
return { sql, bindings };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Compile a DELETE statement
|
|
175
|
+
*/
|
|
176
|
+
compileDelete(query: QueryComponents): CompiledQuery {
|
|
177
|
+
const table = this.wrapTable(query.table);
|
|
178
|
+
const bindings: Binding[] = [];
|
|
179
|
+
|
|
180
|
+
// Compile WHERE clause
|
|
181
|
+
const whereClause = this.compileWheres(query, bindings);
|
|
182
|
+
|
|
183
|
+
const sql = `DELETE FROM ${table}${whereClause ? ` ${whereClause}` : ''}`;
|
|
184
|
+
return { sql, bindings };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Compile a TRUNCATE TABLE statement
|
|
189
|
+
*/
|
|
190
|
+
compileTruncate(table: string): string {
|
|
191
|
+
return `TRUNCATE TABLE ${this.wrapTable(table)} RESTART IDENTITY CASCADE`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Compile an upsert (INSERT ... ON CONFLICT) statement
|
|
196
|
+
*/
|
|
197
|
+
compileUpsert(
|
|
198
|
+
query: QueryComponents,
|
|
199
|
+
values: InsertValues | InsertValues[],
|
|
200
|
+
uniqueColumns: string[],
|
|
201
|
+
updateColumns?: string[]
|
|
202
|
+
): CompiledQuery {
|
|
203
|
+
const { sql, bindings } = this.compileInsert(query, values);
|
|
204
|
+
|
|
205
|
+
const conflictColumns = this.columnize(uniqueColumns);
|
|
206
|
+
const valueArray = Array.isArray(values) ? values : [values];
|
|
207
|
+
const allColumns = Object.keys(valueArray[0]);
|
|
208
|
+
const columnsToUpdate = updateColumns ?? allColumns.filter((col) => !uniqueColumns.includes(col));
|
|
209
|
+
|
|
210
|
+
const updateClause = columnsToUpdate
|
|
211
|
+
.map((col) => `${this.wrap(col)} = EXCLUDED.${this.wrap(col)}`)
|
|
212
|
+
.join(', ');
|
|
213
|
+
|
|
214
|
+
const upsertSql = `${sql} ON CONFLICT (${conflictColumns}) DO UPDATE SET ${updateClause}`;
|
|
215
|
+
return { sql: upsertSql, bindings };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compile a raw expression
|
|
220
|
+
*/
|
|
221
|
+
raw(expression: string): string {
|
|
222
|
+
return expression;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - Query Builder Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the query builder internals that the Grammar compiles.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supported WHERE clause operators
|
|
9
|
+
*/
|
|
10
|
+
export type WhereOperator =
|
|
11
|
+
| '='
|
|
12
|
+
| '!='
|
|
13
|
+
| '<>'
|
|
14
|
+
| '<'
|
|
15
|
+
| '<='
|
|
16
|
+
| '>'
|
|
17
|
+
| '>='
|
|
18
|
+
| 'LIKE'
|
|
19
|
+
| 'NOT LIKE'
|
|
20
|
+
| 'ILIKE'
|
|
21
|
+
| 'NOT ILIKE'
|
|
22
|
+
| 'IN'
|
|
23
|
+
| 'NOT IN'
|
|
24
|
+
| 'BETWEEN'
|
|
25
|
+
| 'NOT BETWEEN'
|
|
26
|
+
| 'IS NULL'
|
|
27
|
+
| 'IS NOT NULL';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Binding value type - values that can be bound to query parameters
|
|
31
|
+
*/
|
|
32
|
+
export type Binding = string | number | boolean | null | Date;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* WHERE clause representation
|
|
36
|
+
*/
|
|
37
|
+
export interface WhereClause {
|
|
38
|
+
type: 'basic' | 'in' | 'null' | 'between' | 'raw' | 'nested';
|
|
39
|
+
column?: string;
|
|
40
|
+
operator?: WhereOperator | string;
|
|
41
|
+
value?: Binding | Binding[];
|
|
42
|
+
values?: Binding[]; // For BETWEEN
|
|
43
|
+
boolean: 'and' | 'or';
|
|
44
|
+
not?: boolean;
|
|
45
|
+
sql?: string; // For raw WHERE
|
|
46
|
+
bindings?: Binding[]; // For raw WHERE
|
|
47
|
+
nested?: WhereClause[]; // For nested WHERE
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* JOIN clause types
|
|
52
|
+
*/
|
|
53
|
+
export type JoinType = 'inner' | 'left' | 'right' | 'cross';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* JOIN clause representation
|
|
57
|
+
*/
|
|
58
|
+
export interface JoinClause {
|
|
59
|
+
type: JoinType;
|
|
60
|
+
table: string;
|
|
61
|
+
first: string; // First column (from main table)
|
|
62
|
+
operator: string;
|
|
63
|
+
second: string; // Second column (from joined table)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ORDER BY direction
|
|
68
|
+
*/
|
|
69
|
+
export type OrderDirection = 'asc' | 'desc';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Raw SQL expression with bindings
|
|
73
|
+
* Used for selectRaw, orderByRaw, etc.
|
|
74
|
+
*/
|
|
75
|
+
export class RawExpression {
|
|
76
|
+
constructor(
|
|
77
|
+
public readonly expression: string,
|
|
78
|
+
public readonly bindings: Binding[] = []
|
|
79
|
+
) { }
|
|
80
|
+
|
|
81
|
+
toString(): string {
|
|
82
|
+
return this.expression;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* ORDER BY clause representation
|
|
88
|
+
*/
|
|
89
|
+
export interface OrderClause {
|
|
90
|
+
column: string;
|
|
91
|
+
direction: OrderDirection;
|
|
92
|
+
raw?: boolean; // If true, column is a raw SQL expression
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Aggregate function types
|
|
97
|
+
*/
|
|
98
|
+
export type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Aggregate representation
|
|
102
|
+
*/
|
|
103
|
+
export interface AggregateClause {
|
|
104
|
+
function: AggregateFunction;
|
|
105
|
+
column: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Full query state object representing all components of a query
|
|
110
|
+
*/
|
|
111
|
+
export interface QueryComponents {
|
|
112
|
+
/** Table name */
|
|
113
|
+
table: string;
|
|
114
|
+
|
|
115
|
+
/** Table alias */
|
|
116
|
+
alias?: string;
|
|
117
|
+
|
|
118
|
+
/** SELECT columns - empty means SELECT * */
|
|
119
|
+
columns: string[];
|
|
120
|
+
|
|
121
|
+
/** Raw SELECT expressions */
|
|
122
|
+
rawSelects: { expression: string; bindings: Binding[] }[];
|
|
123
|
+
|
|
124
|
+
/** DISTINCT flag */
|
|
125
|
+
distinct: boolean;
|
|
126
|
+
|
|
127
|
+
/** WHERE clauses */
|
|
128
|
+
wheres: WhereClause[];
|
|
129
|
+
|
|
130
|
+
/** JOIN clauses */
|
|
131
|
+
joins: JoinClause[];
|
|
132
|
+
|
|
133
|
+
/** ORDER BY clauses */
|
|
134
|
+
orders: OrderClause[];
|
|
135
|
+
|
|
136
|
+
/** GROUP BY columns */
|
|
137
|
+
groups: string[];
|
|
138
|
+
|
|
139
|
+
/** Raw GROUP BY expressions */
|
|
140
|
+
rawGroups: { expression: string; bindings: Binding[] }[];
|
|
141
|
+
|
|
142
|
+
/** ORDER BY bindings (for raw order expressions) */
|
|
143
|
+
orderBindings: Binding[];
|
|
144
|
+
|
|
145
|
+
/** HAVING conditions (similar to WHERE) */
|
|
146
|
+
havings: WhereClause[];
|
|
147
|
+
|
|
148
|
+
/** LIMIT value */
|
|
149
|
+
limit?: number;
|
|
150
|
+
|
|
151
|
+
/** OFFSET value */
|
|
152
|
+
offset?: number;
|
|
153
|
+
|
|
154
|
+
/** Aggregate function */
|
|
155
|
+
aggregate?: AggregateClause;
|
|
156
|
+
|
|
157
|
+
/** LOCK mode */
|
|
158
|
+
lock?: 'for update' | 'for share';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Result of compiling a query - SQL string and bindings
|
|
163
|
+
*/
|
|
164
|
+
export interface CompiledQuery {
|
|
165
|
+
sql: string;
|
|
166
|
+
bindings: Binding[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Insert values - each key is a column name
|
|
171
|
+
*/
|
|
172
|
+
export type InsertValues = Record<string, Binding>;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Update values - each key is a column name
|
|
176
|
+
*/
|
|
177
|
+
export type UpdateValues = Record<string, Binding>;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create empty query components with defaults
|
|
181
|
+
*/
|
|
182
|
+
export function createQueryComponents(table: string): QueryComponents {
|
|
183
|
+
return {
|
|
184
|
+
table,
|
|
185
|
+
columns: [],
|
|
186
|
+
rawSelects: [],
|
|
187
|
+
distinct: false,
|
|
188
|
+
wheres: [],
|
|
189
|
+
joins: [],
|
|
190
|
+
orders: [],
|
|
191
|
+
groups: [],
|
|
192
|
+
rawGroups: [],
|
|
193
|
+
orderBindings: [],
|
|
194
|
+
havings: [],
|
|
195
|
+
};
|
|
196
|
+
}
|