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
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Controller } from '@framework/controller/Controller';
|
|
2
|
+
|
|
3
|
+
export class HealthController extends Controller {
|
|
4
|
+
/**
|
|
5
|
+
* Health check endpoint
|
|
6
|
+
*/
|
|
7
|
+
async check() {
|
|
8
|
+
return this.json({
|
|
9
|
+
status: 'healthy',
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
uptime: process.uptime(),
|
|
12
|
+
service: 'PhoenixJS',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ping endpoint
|
|
18
|
+
*/
|
|
19
|
+
async ping() {
|
|
20
|
+
return this.text('pong');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS - Echo Gateway (Sample)
|
|
3
|
+
*
|
|
4
|
+
* A simple WebSocket gateway that echoes messages back to the sender.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ServerWebSocket } from 'bun';
|
|
8
|
+
import { WebSocketGateway } from '@framework/gateway/WebSocketGateway';
|
|
9
|
+
import type { WebSocketData } from '@framework/gateway/Gateway';
|
|
10
|
+
|
|
11
|
+
export class EchoGateway extends WebSocketGateway {
|
|
12
|
+
readonly name = 'echo';
|
|
13
|
+
readonly path = '/ws/echo';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Called when a connection is opened
|
|
17
|
+
*/
|
|
18
|
+
onOpen(ws: ServerWebSocket<WebSocketData>): void {
|
|
19
|
+
super.onOpen(ws);
|
|
20
|
+
console.log(`[EchoGateway] Client connected: ${ws.data.connectionId}`);
|
|
21
|
+
|
|
22
|
+
// Send welcome message
|
|
23
|
+
this.sendJson(ws, {
|
|
24
|
+
type: 'connected',
|
|
25
|
+
connectionId: ws.data.connectionId,
|
|
26
|
+
message: 'Welcome to EchoGateway!',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Called when a message is received - echo it back
|
|
32
|
+
*/
|
|
33
|
+
onMessage(ws: ServerWebSocket<WebSocketData>, message: string | Buffer): void {
|
|
34
|
+
const text = typeof message === 'string' ? message : message.toString();
|
|
35
|
+
console.log(`[EchoGateway] Received: ${text}`);
|
|
36
|
+
|
|
37
|
+
// Try to parse as JSON
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(text);
|
|
40
|
+
this.sendJson(ws, {
|
|
41
|
+
type: 'echo',
|
|
42
|
+
original: data,
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
// Not JSON, echo as plain text
|
|
47
|
+
this.send(ws, `Echo: ${text}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Called when a connection is closed
|
|
53
|
+
*/
|
|
54
|
+
onClose(ws: ServerWebSocket<WebSocketData>, code: number, reason: string): void {
|
|
55
|
+
console.log(`[EchoGateway] Client disconnected: ${ws.data.connectionId} (${code}: ${reason})`);
|
|
56
|
+
super.onClose(ws, code, reason);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Application } from '@framework/core/Application';
|
|
8
8
|
import { Kernel } from '@framework/core/Kernel';
|
|
9
9
|
import { registerRoutes } from '@/routes/api';
|
|
10
|
+
import { appConfig } from '@/config/app';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Create and configure the application
|
|
@@ -15,13 +16,7 @@ export function createApplication(): Application {
|
|
|
15
16
|
const app = Application.create();
|
|
16
17
|
|
|
17
18
|
// Configure application
|
|
18
|
-
app.configure(
|
|
19
|
-
name: 'PhoenixJS',
|
|
20
|
-
env: (process.env.NODE_ENV as 'development' | 'production' | 'testing') ?? 'development',
|
|
21
|
-
debug: process.env.DEBUG === 'true' || process.env.NODE_ENV !== 'production',
|
|
22
|
-
port: parseInt(process.env.PORT ?? '4000', 10),
|
|
23
|
-
host: process.env.HOST ?? 'localhost',
|
|
24
|
-
});
|
|
19
|
+
app.configure(appConfig);
|
|
25
20
|
|
|
26
21
|
return app;
|
|
27
22
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AppConfig } from '@framework/core/Application';
|
|
2
|
+
|
|
3
|
+
export const appConfig: AppConfig = {
|
|
4
|
+
name: process.env.APP_NAME || 'PhoenixJS',
|
|
5
|
+
env: (process.env.NODE_ENV as 'development' | 'production' | 'testing') || 'development',
|
|
6
|
+
debug: process.env.DEBUG === 'true' || process.env.NODE_ENV !== 'production',
|
|
7
|
+
port: parseInt(process.env.PORT || '4000', 10),
|
|
8
|
+
host: process.env.HOST || 'localhost',
|
|
9
|
+
logging: {
|
|
10
|
+
enabled: process.env.LOGGING_ENABLED !== 'false',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
default: '
|
|
2
|
+
default: process.env.DB_CONNECTION || 'postgres',
|
|
3
3
|
|
|
4
4
|
connections: {
|
|
5
|
+
postgres: {
|
|
6
|
+
driver: 'postgres',
|
|
7
|
+
host: process.env.DB_HOST || 'localhost',
|
|
8
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
9
|
+
database: process.env.DB_DATABASE || 'phoenix',
|
|
10
|
+
username: process.env.DB_USERNAME || 'root',
|
|
11
|
+
password: process.env.DB_PASSWORD || '',
|
|
12
|
+
max: 10, // Max connections in pool
|
|
13
|
+
idle_timeout: 20, // Idle timeout in seconds
|
|
14
|
+
connect_timeout: 10, // Connection timeout in seconds
|
|
15
|
+
},
|
|
16
|
+
|
|
5
17
|
prisma: {
|
|
6
18
|
driver: 'prisma',
|
|
7
19
|
// Prisma usually relies on .env DATABASE_URL, but we can put pool config here
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Schema, Blueprint } from '@framework/database/schema';
|
|
2
|
+
import { Migration } from '@framework/database/migrations';
|
|
3
|
+
|
|
4
|
+
export default class CreateUsersTable implements Migration {
|
|
5
|
+
async up(): Promise<void> {
|
|
6
|
+
await Schema.create('test_users_cli', (table: Blueprint) => {
|
|
7
|
+
table.id();
|
|
8
|
+
table.string('name');
|
|
9
|
+
table.timestamps();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async down(): Promise<void> {
|
|
14
|
+
await Schema.dropIfExists('test_users_cli');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { Schema, Blueprint } from '@framework/database/schema';
|
|
3
|
+
import { Migration } from '@framework/database/migrations';
|
|
4
|
+
|
|
5
|
+
export class TestCliMigration implements Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
await Schema.create('test_cli_table', (table) => {
|
|
8
|
+
table.id();
|
|
9
|
+
table.string('name');
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async down() {
|
|
14
|
+
await Schema.dropIfExists('test_cli_table');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Schema, Blueprint } from '@framework/database/schema';
|
|
2
|
+
import { Migration } from '@framework/database/migrations';
|
|
3
|
+
|
|
4
|
+
export class CreateTestMigrationsTable implements Migration {
|
|
5
|
+
/**
|
|
6
|
+
* Run the migrations.
|
|
7
|
+
*/
|
|
8
|
+
async up(): Promise<void> {
|
|
9
|
+
// await Schema.create('table_name', (table: Blueprint) => {
|
|
10
|
+
// table.id();
|
|
11
|
+
// table.timestamps();
|
|
12
|
+
// });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reverse the migrations.
|
|
17
|
+
*/
|
|
18
|
+
async down(): Promise<void> {
|
|
19
|
+
// await Schema.dropIfExists('table_name');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -3,6 +3,11 @@ import { MakeControllerCommand } from './commands/MakeControllerCommand';
|
|
|
3
3
|
import { MakeValidatorCommand } from './commands/MakeValidatorCommand';
|
|
4
4
|
import { MakeModelCommand } from './commands/MakeModelCommand';
|
|
5
5
|
import { MakeMiddlewareCommand } from './commands/MakeMiddlewareCommand';
|
|
6
|
+
import { MakeMigrationCommand } from '../database/console/MakeMigrationCommand';
|
|
7
|
+
import { MigrateCommand } from '../database/console/MigrateCommand';
|
|
8
|
+
import { MigrateRollbackCommand } from '../database/console/MigrateRollbackCommand';
|
|
9
|
+
import { MigrateResetCommand } from '../database/console/MigrateResetCommand';
|
|
10
|
+
import { MigrateStatusCommand } from '../database/console/MigrateStatusCommand';
|
|
6
11
|
|
|
7
12
|
const app = new ConsoleApplication();
|
|
8
13
|
|
|
@@ -12,5 +17,12 @@ app.register(new MakeValidatorCommand());
|
|
|
12
17
|
app.register(new MakeModelCommand());
|
|
13
18
|
app.register(new MakeMiddlewareCommand());
|
|
14
19
|
|
|
20
|
+
// Database Commands
|
|
21
|
+
app.register(new MakeMigrationCommand());
|
|
22
|
+
app.register(new MigrateCommand());
|
|
23
|
+
app.register(new MigrateRollbackCommand());
|
|
24
|
+
app.register(new MigrateResetCommand());
|
|
25
|
+
app.register(new MigrateStatusCommand());
|
|
26
|
+
|
|
15
27
|
// Run
|
|
16
28
|
await app.run(process.argv);
|
|
@@ -10,6 +10,7 @@ import { PluginManager } from '@framework/plugin/PluginManager';
|
|
|
10
10
|
import { Plugin } from '@framework/plugin/Plugin';
|
|
11
11
|
import { GatewayManager } from '@framework/gateway/GatewayManager';
|
|
12
12
|
import { SecurityManager } from '@framework/security/SecurityManager';
|
|
13
|
+
import { Logger } from '@framework/log/Logger';
|
|
13
14
|
import type { Gateway } from '@framework/gateway/Gateway';
|
|
14
15
|
import type { SecurityConfig } from '@/config/security';
|
|
15
16
|
|
|
@@ -22,6 +23,9 @@ export interface AppConfig {
|
|
|
22
23
|
plugins?: Plugin[];
|
|
23
24
|
gateways?: Gateway[];
|
|
24
25
|
security?: Partial<SecurityConfig>;
|
|
26
|
+
logging?: {
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
};
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
const defaultConfig: AppConfig = {
|
|
@@ -30,6 +34,9 @@ const defaultConfig: AppConfig = {
|
|
|
30
34
|
debug: true,
|
|
31
35
|
port: 4000,
|
|
32
36
|
host: 'localhost',
|
|
37
|
+
logging: {
|
|
38
|
+
enabled: true
|
|
39
|
+
}
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
export class Application extends Container {
|
|
@@ -40,6 +47,7 @@ export class Application extends Container {
|
|
|
40
47
|
private pluginManager: PluginManager;
|
|
41
48
|
private gatewayManager: GatewayManager;
|
|
42
49
|
private securityManager: SecurityManager;
|
|
50
|
+
private logger: Logger;
|
|
43
51
|
|
|
44
52
|
constructor(basePath: string = process.cwd()) {
|
|
45
53
|
super();
|
|
@@ -48,6 +56,7 @@ export class Application extends Container {
|
|
|
48
56
|
this.pluginManager = new PluginManager(this);
|
|
49
57
|
this.gatewayManager = new GatewayManager(this);
|
|
50
58
|
this.securityManager = new SecurityManager(this);
|
|
59
|
+
this.logger = new Logger('Application');
|
|
51
60
|
this.registerBaseBindings();
|
|
52
61
|
}
|
|
53
62
|
|
|
@@ -78,6 +87,7 @@ export class Application extends Container {
|
|
|
78
87
|
this.instance('pluginManager', this.pluginManager);
|
|
79
88
|
this.instance('gatewayManager', this.gatewayManager);
|
|
80
89
|
this.instance('securityManager', this.securityManager);
|
|
90
|
+
this.instance('logger', this.logger);
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
/**
|
|
@@ -87,6 +97,11 @@ export class Application extends Container {
|
|
|
87
97
|
this.config = { ...this.config, ...config };
|
|
88
98
|
this.instance('config', this.config);
|
|
89
99
|
|
|
100
|
+
// Configure Logger
|
|
101
|
+
if (this.config.logging) {
|
|
102
|
+
Logger.configure(this.config.logging);
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
// Register plugins from config
|
|
91
106
|
if (this.config.plugins) {
|
|
92
107
|
this.config.plugins.forEach(plugin => {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - DatabaseManager
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for database operations.
|
|
5
|
+
* Manages connections, provides default connection, and proxies query methods.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Connection, ConnectionConfig } from './connection/Connection';
|
|
9
|
+
import { ConnectionFactory } from './connection/ConnectionFactory';
|
|
10
|
+
|
|
11
|
+
export interface DatabaseConfig {
|
|
12
|
+
default: string;
|
|
13
|
+
connections: Record<string, ConnectionConfig>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class DatabaseManager {
|
|
17
|
+
private factory: ConnectionFactory;
|
|
18
|
+
private connections: Map<string, Connection> = new Map();
|
|
19
|
+
private config: DatabaseConfig;
|
|
20
|
+
|
|
21
|
+
constructor(config: DatabaseConfig) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.factory = new ConnectionFactory();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get a database connection by name
|
|
28
|
+
* Creates the connection if it doesn't exist
|
|
29
|
+
*/
|
|
30
|
+
connection(name?: string): Connection {
|
|
31
|
+
const connectionName = name || this.config.default;
|
|
32
|
+
|
|
33
|
+
if (!this.connections.has(connectionName)) {
|
|
34
|
+
const connectionConfig = this.config.connections[connectionName];
|
|
35
|
+
if (!connectionConfig) {
|
|
36
|
+
throw new Error(`Database connection [${connectionName}] not configured.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const connection = this.factory.make(connectionConfig);
|
|
40
|
+
this.connections.set(connectionName, connection);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return this.connections.get(connectionName)!;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Connect to a database by name (or default)
|
|
48
|
+
*/
|
|
49
|
+
async connect(name?: string): Promise<Connection> {
|
|
50
|
+
const conn = this.connection(name);
|
|
51
|
+
await conn.connect();
|
|
52
|
+
return conn;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Disconnect a specific connection
|
|
57
|
+
*/
|
|
58
|
+
async disconnect(name?: string): Promise<void> {
|
|
59
|
+
const connectionName = name || this.config.default;
|
|
60
|
+
const conn = this.connections.get(connectionName);
|
|
61
|
+
if (conn) {
|
|
62
|
+
await conn.disconnect();
|
|
63
|
+
this.connections.delete(connectionName);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Disconnect all connections
|
|
69
|
+
*/
|
|
70
|
+
async disconnectAll(): Promise<void> {
|
|
71
|
+
const disconnectPromises = Array.from(this.connections.values()).map((conn) =>
|
|
72
|
+
conn.disconnect()
|
|
73
|
+
);
|
|
74
|
+
await Promise.all(disconnectPromises);
|
|
75
|
+
this.connections.clear();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Execute a SELECT query on the default connection
|
|
80
|
+
*/
|
|
81
|
+
async query<T = unknown>(sql: string, bindings?: unknown[]): Promise<T[]> {
|
|
82
|
+
return this.connection().query<T>(sql, bindings);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute an INSERT/UPDATE/DELETE on the default connection
|
|
87
|
+
*/
|
|
88
|
+
async execute(sql: string, bindings?: unknown[]): Promise<number> {
|
|
89
|
+
return this.connection().execute(sql, bindings);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get a single row from the default connection
|
|
94
|
+
*/
|
|
95
|
+
async get<T = unknown>(sql: string, bindings?: unknown[]): Promise<T | null> {
|
|
96
|
+
return this.connection().get<T>(sql, bindings);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Execute a transaction on the default connection
|
|
101
|
+
*/
|
|
102
|
+
async transaction<T>(callback: (tx: unknown) => Promise<T>): Promise<T> {
|
|
103
|
+
return this.connection().transaction(callback);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Begin a fluent query against a database table on the default connection
|
|
108
|
+
*/
|
|
109
|
+
table(table: string): any {
|
|
110
|
+
return this.connection().table(table);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the default connection name
|
|
115
|
+
*/
|
|
116
|
+
getDefaultConnection(): string {
|
|
117
|
+
return this.config.default;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all connection names
|
|
122
|
+
*/
|
|
123
|
+
getConnectionNames(): string[] {
|
|
124
|
+
return Object.keys(this.config.connections);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if a connection is active
|
|
129
|
+
*/
|
|
130
|
+
hasConnection(name: string): boolean {
|
|
131
|
+
return this.connections.has(name);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - Connection Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstract interface for database connections.
|
|
5
|
+
* All database drivers must implement this interface.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ConnectionConfig {
|
|
9
|
+
driver: string;
|
|
10
|
+
host?: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
database?: string;
|
|
13
|
+
username?: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Connection {
|
|
19
|
+
/**
|
|
20
|
+
* Get the driver name
|
|
21
|
+
*/
|
|
22
|
+
readonly driver: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Connect to the database
|
|
26
|
+
*/
|
|
27
|
+
connect(): Promise<void>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Disconnect from the database
|
|
31
|
+
*/
|
|
32
|
+
disconnect(): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if connected
|
|
36
|
+
*/
|
|
37
|
+
isConnected(): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute a SELECT query and return results
|
|
41
|
+
* @param sql SQL query string
|
|
42
|
+
* @param bindings Query parameter bindings
|
|
43
|
+
*/
|
|
44
|
+
query<T = unknown>(sql: string, bindings?: unknown[]): Promise<T[]>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Execute an INSERT/UPDATE/DELETE query and return affected rows count
|
|
48
|
+
* @param sql SQL statement string
|
|
49
|
+
* @param bindings Query parameter bindings
|
|
50
|
+
*/
|
|
51
|
+
execute(sql: string, bindings?: unknown[]): Promise<number>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get a single row
|
|
55
|
+
* @param sql SQL query string
|
|
56
|
+
* @param bindings Query parameter bindings
|
|
57
|
+
*/
|
|
58
|
+
get<T = unknown>(sql: string, bindings?: unknown[]): Promise<T | null>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute operations within a transaction
|
|
62
|
+
* @param callback Transaction callback that receives a transaction SQL instance
|
|
63
|
+
*/
|
|
64
|
+
transaction<T>(callback: (tx: unknown) => Promise<T>): Promise<T>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Begin a fluent query against a database table
|
|
68
|
+
* @param table The table name
|
|
69
|
+
*/
|
|
70
|
+
table(table: string): any; // Return any to avoid circular type dependency for now, or use Builder interface if available
|
|
71
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - ConnectionFactory
|
|
3
|
+
*
|
|
4
|
+
* Factory class for creating database connections based on configuration.
|
|
5
|
+
* Supports driver-based connection instantiation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Connection, ConnectionConfig } from './Connection';
|
|
9
|
+
import { PostgresConnection, type PostgresConnectionConfig } from './PostgresConnection';
|
|
10
|
+
|
|
11
|
+
export class ConnectionFactory {
|
|
12
|
+
/**
|
|
13
|
+
* Create a connection instance based on the driver configuration
|
|
14
|
+
*/
|
|
15
|
+
make(config: ConnectionConfig): Connection {
|
|
16
|
+
switch (config.driver) {
|
|
17
|
+
case 'postgres':
|
|
18
|
+
return new PostgresConnection(config as PostgresConnectionConfig);
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unsupported database driver: ${config.driver}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get list of supported drivers
|
|
26
|
+
*/
|
|
27
|
+
getSupportedDrivers(): string[] {
|
|
28
|
+
return ['postgres'];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - PostgresConnection
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL connection implementation using postgres.js (porsager/postgres).
|
|
5
|
+
* Zero-dependency, fastest Postgres driver for Node.js/Bun.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import postgres, { type Sql, type TransactionSql } from 'postgres';
|
|
9
|
+
import type { Connection, ConnectionConfig } from './Connection';
|
|
10
|
+
import { Builder } from '@framework/database/query/Builder';
|
|
11
|
+
import { PostgresGrammar } from '@framework/database/query/grammars/PostgresGrammar';
|
|
12
|
+
|
|
13
|
+
export interface PostgresConnectionConfig extends ConnectionConfig {
|
|
14
|
+
driver: 'postgres';
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
database: string;
|
|
18
|
+
username: string;
|
|
19
|
+
password: string;
|
|
20
|
+
max?: number; // Max connections in pool
|
|
21
|
+
idle_timeout?: number; // Idle connection timeout in seconds
|
|
22
|
+
connect_timeout?: number; // Connection timeout in seconds
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class PostgresConnection implements Connection {
|
|
26
|
+
readonly driver = 'postgres';
|
|
27
|
+
|
|
28
|
+
private sql: Sql | null = null;
|
|
29
|
+
private connected = false;
|
|
30
|
+
private config: PostgresConnectionConfig;
|
|
31
|
+
|
|
32
|
+
constructor(config: PostgresConnectionConfig) {
|
|
33
|
+
this.config = config;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Connect to the PostgreSQL database
|
|
38
|
+
*/
|
|
39
|
+
async connect(): Promise<void> {
|
|
40
|
+
if (this.connected && this.sql) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.sql = postgres({
|
|
45
|
+
host: this.config.host,
|
|
46
|
+
port: this.config.port,
|
|
47
|
+
database: this.config.database,
|
|
48
|
+
username: this.config.username,
|
|
49
|
+
password: this.config.password,
|
|
50
|
+
max: this.config.max ?? 10,
|
|
51
|
+
idle_timeout: this.config.idle_timeout ?? 20,
|
|
52
|
+
connect_timeout: this.config.connect_timeout ?? 10,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Test the connection
|
|
56
|
+
await this.sql`SELECT 1`;
|
|
57
|
+
this.connected = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Disconnect from the database
|
|
62
|
+
*/
|
|
63
|
+
async disconnect(): Promise<void> {
|
|
64
|
+
if (this.sql) {
|
|
65
|
+
await this.sql.end();
|
|
66
|
+
this.sql = null;
|
|
67
|
+
this.connected = false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if connected to the database
|
|
73
|
+
*/
|
|
74
|
+
isConnected(): boolean {
|
|
75
|
+
return this.connected;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Execute a SELECT query and return results
|
|
80
|
+
*/
|
|
81
|
+
async query<T = unknown>(sql: string, bindings: unknown[] = []): Promise<T[]> {
|
|
82
|
+
this.ensureConnected();
|
|
83
|
+
const result = await this.sql!.unsafe(sql, bindings as (string | number | boolean | null)[]);
|
|
84
|
+
return result as unknown as T[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Execute an INSERT/UPDATE/DELETE query and return affected rows count
|
|
89
|
+
*/
|
|
90
|
+
async execute(sql: string, bindings: unknown[] = []): Promise<number> {
|
|
91
|
+
this.ensureConnected();
|
|
92
|
+
const result = await this.sql!.unsafe(sql, bindings as (string | number | boolean | null)[]);
|
|
93
|
+
return result.count;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get a single row
|
|
98
|
+
*/
|
|
99
|
+
async get<T = unknown>(sql: string, bindings: unknown[] = []): Promise<T | null> {
|
|
100
|
+
const results = await this.query<T>(sql, bindings);
|
|
101
|
+
return results.length > 0 ? results[0] : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Execute operations within a transaction.
|
|
106
|
+
* The callback receives a transaction-scoped SQL instance.
|
|
107
|
+
*/
|
|
108
|
+
async transaction<T>(callback: (sql: TransactionSql) => Promise<T>): Promise<T> {
|
|
109
|
+
this.ensureConnected();
|
|
110
|
+
|
|
111
|
+
const result = await this.sql!.begin(async (txSql) => {
|
|
112
|
+
return callback(txSql);
|
|
113
|
+
});
|
|
114
|
+
return result as T;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Execute a transaction query (for use inside transactions)
|
|
119
|
+
*/
|
|
120
|
+
async txQuery<T = unknown>(
|
|
121
|
+
txSql: TransactionSql,
|
|
122
|
+
sql: string,
|
|
123
|
+
bindings: unknown[] = []
|
|
124
|
+
): Promise<T[]> {
|
|
125
|
+
const result = await txSql.unsafe(sql, bindings as (string | number | boolean | null)[]);
|
|
126
|
+
return result as unknown as T[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Execute a transaction statement (for use inside transactions)
|
|
131
|
+
*/
|
|
132
|
+
async txExecute(txSql: TransactionSql, sql: string, bindings: unknown[] = []): Promise<number> {
|
|
133
|
+
const result = await txSql.unsafe(sql, bindings as (string | number | boolean | null)[]);
|
|
134
|
+
return result.count;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the underlying postgres.js SQL instance
|
|
139
|
+
*/
|
|
140
|
+
getSql(): Sql | null {
|
|
141
|
+
return this.sql;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Begin a fluent query against a database table
|
|
146
|
+
*/
|
|
147
|
+
table(table: string): Builder {
|
|
148
|
+
return new Builder(this, new PostgresGrammar(), table);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Ensure we have an active connection
|
|
153
|
+
*/
|
|
154
|
+
private ensureConnected(): void {
|
|
155
|
+
if (!this.connected || !this.sql) {
|
|
156
|
+
throw new Error('Database not connected. Call connect() first.');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|