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.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/template/app/controllers/HealthController.ts +22 -0
  3. package/template/app/gateways/EchoGateway.ts +58 -0
  4. package/template/bootstrap/app.ts +2 -7
  5. package/template/config/app.ts +12 -0
  6. package/template/config/database.ts +13 -1
  7. package/template/database/migrations/2024_01_01_000000_create_test_users_cli_table.ts +16 -0
  8. package/template/database/migrations/20260108164611_TestCliMigration.ts +16 -0
  9. package/template/database/migrations/2026_01_08_16_46_11_CreateTestMigrationsTable.ts +21 -0
  10. package/template/framework/cli/artisan.ts +12 -0
  11. package/template/framework/core/Application.ts +15 -0
  12. package/template/framework/database/DatabaseManager.ts +133 -0
  13. package/template/framework/database/connection/Connection.ts +71 -0
  14. package/template/framework/database/connection/ConnectionFactory.ts +30 -0
  15. package/template/framework/database/connection/PostgresConnection.ts +159 -0
  16. package/template/framework/database/console/MakeMigrationCommand.ts +58 -0
  17. package/template/framework/database/console/MigrateCommand.ts +32 -0
  18. package/template/framework/database/console/MigrateResetCommand.ts +31 -0
  19. package/template/framework/database/console/MigrateRollbackCommand.ts +31 -0
  20. package/template/framework/database/console/MigrateStatusCommand.ts +38 -0
  21. package/template/framework/database/migrations/DatabaseMigrationRepository.ts +122 -0
  22. package/template/framework/database/migrations/Migration.ts +5 -0
  23. package/template/framework/database/migrations/MigrationRepository.ts +46 -0
  24. package/template/framework/database/migrations/Migrator.ts +249 -0
  25. package/template/framework/database/migrations/index.ts +4 -0
  26. package/template/framework/database/orm/BelongsTo.ts +246 -0
  27. package/template/framework/database/orm/BelongsToMany.ts +570 -0
  28. package/template/framework/database/orm/Builder.ts +160 -0
  29. package/template/framework/database/orm/EagerLoadingBuilder.ts +324 -0
  30. package/template/framework/database/orm/HasMany.ts +303 -0
  31. package/template/framework/database/orm/HasManyThrough.ts +282 -0
  32. package/template/framework/database/orm/HasOne.ts +201 -0
  33. package/template/framework/database/orm/HasOneThrough.ts +281 -0
  34. package/template/framework/database/orm/Model.ts +1766 -0
  35. package/template/framework/database/orm/Relation.ts +342 -0
  36. package/template/framework/database/orm/Scope.ts +14 -0
  37. package/template/framework/database/orm/SoftDeletes.ts +160 -0
  38. package/template/framework/database/orm/index.ts +54 -0
  39. package/template/framework/database/orm/scopes/SoftDeletingScope.ts +58 -0
  40. package/template/framework/database/pagination/LengthAwarePaginator.ts +55 -0
  41. package/template/framework/database/pagination/Paginator.ts +110 -0
  42. package/template/framework/database/pagination/index.ts +2 -0
  43. package/template/framework/database/query/Builder.ts +918 -0
  44. package/template/framework/database/query/DB.ts +139 -0
  45. package/template/framework/database/query/grammars/Grammar.ts +430 -0
  46. package/template/framework/database/query/grammars/PostgresGrammar.ts +224 -0
  47. package/template/framework/database/query/grammars/index.ts +6 -0
  48. package/template/framework/database/query/index.ts +8 -0
  49. package/template/framework/database/query/types.ts +196 -0
  50. package/template/framework/database/schema/Blueprint.ts +478 -0
  51. package/template/framework/database/schema/Schema.ts +149 -0
  52. package/template/framework/database/schema/SchemaBuilder.ts +152 -0
  53. package/template/framework/database/schema/grammars/PostgresSchemaGrammar.ts +293 -0
  54. package/template/framework/database/schema/grammars/index.ts +5 -0
  55. package/template/framework/database/schema/index.ts +9 -0
  56. package/template/framework/log/Logger.ts +195 -0
  57. package/template/package.json +4 -1
  58. package/template/routes/api.ts +13 -35
  59. package/template/app/controllers/ExampleController.ts +0 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-phoenixjs",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Create a new PhoenixJS project - A TypeScript framework inspired by Laravel, powered by Bun",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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: 'prisma',
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
+ }