@hexaijs/postgres 0.3.0 → 0.5.0

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/README.md +78 -24
  2. package/dist/helpers-vPAudN_S.d.ts +125 -0
  3. package/dist/index.d.ts +64 -7
  4. package/dist/index.js +828 -28
  5. package/dist/index.js.map +1 -1
  6. package/dist/test.d.ts +13 -12
  7. package/dist/test.js +683 -246
  8. package/dist/test.js.map +1 -1
  9. package/package.json +8 -7
  10. package/dist/config/index.d.ts +0 -3
  11. package/dist/config/index.d.ts.map +0 -1
  12. package/dist/config/index.js +0 -19
  13. package/dist/config/index.js.map +0 -1
  14. package/dist/config/postgres-config-spec.d.ts +0 -32
  15. package/dist/config/postgres-config-spec.d.ts.map +0 -1
  16. package/dist/config/postgres-config-spec.js +0 -49
  17. package/dist/config/postgres-config-spec.js.map +0 -1
  18. package/dist/config/postgres-config.d.ts +0 -59
  19. package/dist/config/postgres-config.d.ts.map +0 -1
  20. package/dist/config/postgres-config.js +0 -181
  21. package/dist/config/postgres-config.js.map +0 -1
  22. package/dist/helpers.d.ts +0 -57
  23. package/dist/helpers.d.ts.map +0 -1
  24. package/dist/helpers.js +0 -276
  25. package/dist/helpers.js.map +0 -1
  26. package/dist/index.d.ts.map +0 -1
  27. package/dist/postgres-event-store.d.ts +0 -18
  28. package/dist/postgres-event-store.d.ts.map +0 -1
  29. package/dist/postgres-event-store.js +0 -83
  30. package/dist/postgres-event-store.js.map +0 -1
  31. package/dist/postgres-unit-of-work.d.ts +0 -18
  32. package/dist/postgres-unit-of-work.d.ts.map +0 -1
  33. package/dist/postgres-unit-of-work.js +0 -265
  34. package/dist/postgres-unit-of-work.js.map +0 -1
  35. package/dist/run-hexai-migrations.d.ts +0 -3
  36. package/dist/run-hexai-migrations.d.ts.map +0 -1
  37. package/dist/run-hexai-migrations.js +0 -17
  38. package/dist/run-hexai-migrations.js.map +0 -1
  39. package/dist/run-migrations.d.ts +0 -11
  40. package/dist/run-migrations.d.ts.map +0 -1
  41. package/dist/run-migrations.js +0 -202
  42. package/dist/run-migrations.js.map +0 -1
  43. package/dist/test-fixtures/config.d.ts +0 -5
  44. package/dist/test-fixtures/config.d.ts.map +0 -1
  45. package/dist/test-fixtures/config.js +0 -14
  46. package/dist/test-fixtures/config.js.map +0 -1
  47. package/dist/test-fixtures/hooks.d.ts +0 -8
  48. package/dist/test-fixtures/hooks.d.ts.map +0 -1
  49. package/dist/test-fixtures/hooks.js +0 -77
  50. package/dist/test-fixtures/hooks.js.map +0 -1
  51. package/dist/test-fixtures/index.d.ts +0 -3
  52. package/dist/test-fixtures/index.d.ts.map +0 -1
  53. package/dist/test-fixtures/index.js +0 -19
  54. package/dist/test-fixtures/index.js.map +0 -1
  55. package/dist/test.d.ts.map +0 -1
  56. package/dist/types.d.ts +0 -14
  57. package/dist/types.d.ts.map +0 -1
  58. package/dist/types.js +0 -11
  59. package/dist/types.js.map +0 -1
package/README.md CHANGED
@@ -30,20 +30,29 @@ The `PostgresUnitOfWork` implements `UnitOfWork` from `@hexaijs/core`. It manage
30
30
 
31
31
  ```typescript
32
32
  import * as pg from "pg";
33
- import { PostgresUnitOfWork } from "@hexaijs/postgres";
33
+ import { createPostgresUnitOfWork } from "@hexaijs/postgres";
34
34
 
35
- // Create with a client factory
35
+ // From connection pool (recommended for production)
36
36
  const pool = new pg.Pool({ connectionString: "postgres://..." });
37
- const unitOfWork = new PostgresUnitOfWork(
37
+ const unitOfWork = createPostgresUnitOfWork(pool);
38
+
39
+ // From connection string
40
+ const unitOfWork = createPostgresUnitOfWork("postgres://user:pass@localhost:5432/mydb");
41
+
42
+ // From PostgresConfig
43
+ const unitOfWork = createPostgresUnitOfWork(PostgresConfig.fromEnv("DB"));
44
+ ```
45
+
46
+ For advanced use cases, you can use `DefaultPostgresUnitOfWork` directly:
47
+
48
+ ```typescript
49
+ import { DefaultPostgresUnitOfWork } from "@hexaijs/postgres";
50
+
51
+ // Custom client factory with custom cleanup
52
+ const unitOfWork = new DefaultPostgresUnitOfWork(
38
53
  () => new pg.Client({ connectionString: "postgres://..." }),
39
54
  (client) => client.end() // cleanup function
40
55
  );
41
-
42
- // Or use a connection pool
43
- const pooledUnitOfWork = new PostgresUnitOfWork(
44
- async () => await pool.connect(),
45
- (client) => (client as pg.PoolClient).release()
46
- );
47
56
  ```
48
57
 
49
58
  The client factory creates a new client for each transaction. The optional cleanup function runs after the transaction completes (commit or rollback).
@@ -71,30 +80,30 @@ const client = ctx.getUnitOfWork().getClient();
71
80
  await client.query("UPDATE orders SET status = $1 WHERE id = $2", ["confirmed", orderId]);
72
81
  ```
73
82
 
74
- ### Query Execution (No Transaction)
83
+ ### Client Access Without Transaction
75
84
 
76
- Use `query()` for read-only operations without transaction overhead. This implements the `QueryableUnitOfWork` interface from `@hexaijs/core`.
85
+ Use `withClient()` for operations without transaction overhead. Useful for read-only queries or when you need direct client access.
77
86
 
78
87
  ```typescript
79
88
  // Simple read without transaction (autocommit)
80
- const user = await unitOfWork.query(async (client) => {
89
+ const user = await unitOfWork.withClient(async (client) => {
81
90
  const result = await client.query("SELECT * FROM users WHERE id = $1", [userId]);
82
91
  return result.rows[0];
83
92
  });
84
93
  ```
85
94
 
86
- The `query()` method is context-aware:
95
+ The `withClient()` method is context-aware:
87
96
 
88
97
  | Context | Behavior |
89
98
  |---------|----------|
90
- | Outside transaction | New connection from factory → query → cleanup |
99
+ | Outside transaction | New connection from factory → work → cleanup |
91
100
  | Inside transaction | Reuses existing transaction's client |
92
101
 
93
- This means you can safely use `query()` anywhere in your code:
102
+ This means you can safely use `withClient()` anywhere in your code:
94
103
 
95
104
  ```typescript
96
105
  // Outside any transaction - gets its own connection
97
- const users = await unitOfWork.query(async (client) => {
106
+ const users = await unitOfWork.withClient(async (client) => {
98
107
  return await client.query("SELECT * FROM users");
99
108
  });
100
109
 
@@ -103,19 +112,19 @@ await unitOfWork.wrap(async (txClient) => {
103
112
  await txClient.query("INSERT INTO orders (id) VALUES ($1)", [orderId]);
104
113
 
105
114
  // Uses the same txClient, sees uncommitted changes
106
- const order = await unitOfWork.query(async (client) => {
115
+ const order = await unitOfWork.withClient(async (client) => {
107
116
  // client === txClient
108
117
  return await client.query("SELECT * FROM orders WHERE id = $1", [orderId]);
109
118
  });
110
119
  });
111
120
  ```
112
121
 
113
- **When to use `wrap()` vs `query()`:**
122
+ **When to use `wrap()` vs `withClient()`:**
114
123
 
115
124
  | Method | Transaction | Overhead | Use Case |
116
125
  |--------|-------------|----------|----------|
117
126
  | `wrap()` | Yes | BEGIN + COMMIT | Commands (INSERT, UPDATE, DELETE) |
118
- | `query()` | No | Connection only | Queries (SELECT) |
127
+ | `withClient()` | No | Connection only | Queries (SELECT) |
119
128
 
120
129
  ### Transaction Propagation
121
130
 
@@ -322,7 +331,7 @@ await ensureConnection(client); // Safe to call multiple times
322
331
 
323
332
  ### PostgresUnitOfWorkForTesting
324
333
 
325
- A test-specific `QueryableUnitOfWork` implementation that runs inside an external transaction. This allows tests to rollback all changes after each test, keeping the database clean without truncating tables.
334
+ A test-specific `PostgresUnitOfWork` implementation that runs inside an external transaction. This allows tests to rollback all changes after each test, keeping the database clean without truncating tables.
326
335
 
327
336
  ```typescript
328
337
  import { PostgresUnitOfWorkForTesting } from "@hexaijs/postgres/test";
@@ -368,7 +377,7 @@ describe("OrderService", () => {
368
377
 
369
378
  **Key behaviors:**
370
379
 
371
- - **`query()` method**: Uses the test client directly, always within the external transaction context.
380
+ - **`withClient()` method**: Uses the test client directly, always within the external transaction context.
372
381
  - **abortError propagation**: When a nested `EXISTING` operation throws (even if caught), the entire transaction is marked as aborted and will rollback - matching production behavior.
373
382
  - **NESTED savepoints**: `Propagation.NESTED` creates independent savepoints that can rollback without affecting the parent.
374
383
  - **Propagation.NEW**: Logs a warning and creates a new savepoint instead (true separate transactions are not possible within the external transaction).
@@ -413,8 +422,10 @@ await uow.wrap(async (c) => {
413
422
 
414
423
  | Export | Description |
415
424
  |--------|-------------|
416
- | `PostgresUnitOfWork` | Transaction and query management implementing `QueryableUnitOfWork` |
417
- | `PostgresUnitOfWorkForTesting` | Test-specific QueryableUnitOfWork that runs inside external transaction |
425
+ | `createPostgresUnitOfWork` | Factory function to create PostgresUnitOfWork from Pool or Config |
426
+ | `PostgresUnitOfWork` | Interface extending UnitOfWork with `withClient()` method |
427
+ | `DefaultPostgresUnitOfWork` | Default implementation of PostgresUnitOfWork with transaction management |
428
+ | `PostgresUnitOfWorkForTesting` | Test-specific PostgresUnitOfWork that runs inside external transaction |
418
429
  | `PostgresEventStore` | Event store implementation with batch insert support |
419
430
  | `PostgresConfig` | Immutable configuration with builder pattern |
420
431
  | `postgresConfig` | Config spec for `defineConfig` integration |
@@ -425,8 +436,51 @@ await uow.wrap(async (c) => {
425
436
  | `IsolationLevel` | Transaction isolation level enum |
426
437
  | `ensureConnection` | Safe connection helper |
427
438
 
439
+ ## Migration Guide
440
+
441
+ ### v0.3.x → v0.4.0
442
+
443
+ **Breaking Change: `PostgresUnitOfWork` class renamed**
444
+
445
+ `PostgresUnitOfWork` is now an interface. The actual implementation is `DefaultPostgresUnitOfWork`.
446
+
447
+ ```typescript
448
+ // Before (v0.3.x)
449
+ import { PostgresUnitOfWork } from "@hexaijs/postgres";
450
+ const uow = new PostgresUnitOfWork(factory, cleanup);
451
+
452
+ // After (v0.4.0)
453
+ import { DefaultPostgresUnitOfWork } from "@hexaijs/postgres";
454
+ const uow = new DefaultPostgresUnitOfWork(factory, cleanup);
455
+
456
+ // Type usage (unchanged)
457
+ function doSomething(uow: PostgresUnitOfWork) { ... }
458
+ ```
459
+
460
+ **Why this change?**
461
+
462
+ The interface allows both `DefaultPostgresUnitOfWork` and `PostgresUnitOfWorkForTesting` to be used interchangeably where `PostgresUnitOfWork` type is expected.
463
+
464
+ **Breaking Change: `query()` renamed to `withClient()`**
465
+
466
+ The `query()` method has been renamed to `withClient()` for clarity. The name `query()` was confusing because inside the callback, you also call `client.query()`.
467
+
468
+ ```typescript
469
+ // Before (v0.3.x)
470
+ const user = await unitOfWork.query(async (client) => {
471
+ return client.query("SELECT * FROM users WHERE id = $1", [userId]);
472
+ });
473
+
474
+ // After (v0.4.0)
475
+ const user = await unitOfWork.withClient(async (client) => {
476
+ return client.query("SELECT * FROM users WHERE id = $1", [userId]);
477
+ });
478
+ ```
479
+
480
+ The `QueryableUnitOfWork` interface has been removed from `@hexaijs/core`. The `withClient()` method is now specific to `@hexaijs/postgres`.
481
+
428
482
  ## See Also
429
483
 
430
- - [@hexaijs/core](../core/README.md) - Core interfaces (`UnitOfWork`, `QueryableUnitOfWork`, `EventStore`, `Propagation`)
484
+ - [@hexaijs/core](../core/README.md) - Core interfaces (`UnitOfWork`, `EventStore`, `Propagation`)
431
485
  - [@hexaijs/sqlite](../sqlite/README.md) - SQLite implementation for testing
432
486
  - [@hexaijs/application](../application/README.md) - Application context that provides `getUnitOfWork()`
@@ -0,0 +1,125 @@
1
+ import * as pg from 'pg';
2
+ import { BaseUnitOfWorkOptions, UnitOfWork } from '@hexaijs/core';
3
+ import { DatabaseConfig } from 'ezcfg';
4
+
5
+ interface PoolOptions {
6
+ size?: number;
7
+ connectionTimeout?: number;
8
+ idleTimeout?: number;
9
+ }
10
+ interface FromEnvOptions {
11
+ /**
12
+ * Environment variable loading mode.
13
+ * - "url": Load from {PREFIX}_URL (default)
14
+ * - "fields": Load from {PREFIX}_HOST, {PREFIX}_PORT, {PREFIX}_DATABASE, {PREFIX}_USER, {PREFIX}_PASSWORD
15
+ */
16
+ mode?: "url" | "fields";
17
+ }
18
+ declare class PostgresConfig implements DatabaseConfig {
19
+ readonly host: string;
20
+ readonly database: string;
21
+ readonly user: string;
22
+ readonly port: number;
23
+ readonly password?: string;
24
+ readonly pool?: PoolOptions;
25
+ constructor(config: {
26
+ database: string;
27
+ user?: string;
28
+ host?: string;
29
+ port?: number;
30
+ password?: string;
31
+ pool?: PoolOptions;
32
+ });
33
+ static fromUrl(value: string): PostgresConfig;
34
+ /**
35
+ * Creates a PostgresConfig from environment variables.
36
+ *
37
+ * @param prefix - Environment variable prefix
38
+ * @param options - Loading options (mode: "url" | "fields")
39
+ * @throws Error if required environment variables are not set
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // URL mode (default): reads ASSIGNMENT_DB_URL
44
+ * const config = PostgresConfig.fromEnv("ASSIGNMENT_DB");
45
+ *
46
+ * // Fields mode: reads POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DATABASE, POSTGRES_USER, POSTGRES_PASSWORD
47
+ * const config = PostgresConfig.fromEnv("POSTGRES", { mode: "fields" });
48
+ * ```
49
+ */
50
+ static fromEnv(prefix: string, options?: FromEnvOptions): PostgresConfig;
51
+ private static parseUrl;
52
+ withDatabase(database: string): PostgresConfig;
53
+ withUser(user: string): PostgresConfig;
54
+ withPassword(password: string): PostgresConfig;
55
+ withHost(host: string): PostgresConfig;
56
+ withPort(port: number): PostgresConfig;
57
+ withPoolSize(size: number): PostgresConfig;
58
+ withConnectionTimeout(connectionTimeout: number): PostgresConfig;
59
+ withIdleTimeout(idleTimeout: number): PostgresConfig;
60
+ toString(): string;
61
+ }
62
+
63
+ type ClientFactory = () => pg.ClientBase | Promise<pg.ClientBase>;
64
+ type ClientCleanUp = (client: pg.ClientBase) => void | Promise<void>;
65
+ declare enum IsolationLevel {
66
+ READ_UNCOMMITTED = "read uncommitted",
67
+ READ_COMMITTED = "read committed",
68
+ REPEATABLE_READ = "repeatable read",
69
+ SERIALIZABLE = "serializable"
70
+ }
71
+ interface PostgresTransactionOptions extends BaseUnitOfWorkOptions {
72
+ isolationLevel?: IsolationLevel;
73
+ }
74
+
75
+ interface PostgresUnitOfWork extends UnitOfWork<pg.ClientBase, PostgresTransactionOptions> {
76
+ withClient<T>(fn: (client: pg.ClientBase) => Promise<T>): Promise<T>;
77
+ }
78
+ declare class DefaultPostgresUnitOfWork implements PostgresUnitOfWork {
79
+ private clientFactory;
80
+ private clientCleanUp?;
81
+ private transactionStorage;
82
+ constructor(clientFactory: ClientFactory, clientCleanUp?: ClientCleanUp | undefined);
83
+ getClient(): pg.ClientBase;
84
+ wrap<T = unknown>(fn: (client: pg.ClientBase) => Promise<T>, options?: Partial<PostgresTransactionOptions>): Promise<T>;
85
+ withClient<T>(fn: (client: pg.ClientBase) => Promise<T>): Promise<T>;
86
+ private getCurrentTransaction;
87
+ private resolveOptions;
88
+ private resolveTransaction;
89
+ private createTransaction;
90
+ private executeInContext;
91
+ }
92
+ declare function createPostgresUnitOfWork(pool: pg.Pool): PostgresUnitOfWork;
93
+ declare function createPostgresUnitOfWork(config: PostgresConfig | string): PostgresUnitOfWork;
94
+
95
+ declare class ClientWrapper {
96
+ protected client: pg.Client;
97
+ getClient(): pg.Client;
98
+ constructor(urlOrClient: PostgresConfig | string | pg.Client);
99
+ protected withClient<T = unknown>(work: (client: pg.Client) => Promise<T>): Promise<T>;
100
+ query<R = any>(query: string, params?: any[]): Promise<Array<R>>;
101
+ close(): Promise<void>;
102
+ }
103
+ declare class DatabaseManager extends ClientWrapper {
104
+ createDatabase(name: string): Promise<void>;
105
+ dropDatabase(name: string): Promise<void>;
106
+ }
107
+ declare class TableManager extends ClientWrapper {
108
+ getTableSchema(tableName: string): Promise<Array<{
109
+ column: string;
110
+ type: string;
111
+ }>>;
112
+ tableExists(tableName: string): Promise<boolean>;
113
+ createTable(name: string, columns: Array<{
114
+ name: string;
115
+ property: string;
116
+ }>): Promise<void>;
117
+ dropTable(name: string): Promise<void>;
118
+ truncateTable(name: string): Promise<void>;
119
+ truncateAllTables(): Promise<void>;
120
+ dropAllTables(): Promise<void>;
121
+ private getTableNames;
122
+ }
123
+ declare function ensureConnection(client: pg.ClientBase): Promise<void>;
124
+
125
+ export { type ClientCleanUp as C, DatabaseManager as D, type FromEnvOptions as F, IsolationLevel as I, PostgresConfig as P, TableManager as T, type ClientFactory as a, ClientWrapper as b, DefaultPostgresUnitOfWork as c, type PoolOptions as d, type PostgresTransactionOptions as e, type PostgresUnitOfWork as f, createPostgresUnitOfWork as g, ensureConnection as h };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,64 @@
1
- export * from "./postgres-unit-of-work";
2
- export * from "./run-migrations";
3
- export * from "./run-hexai-migrations";
4
- export { ClientWrapper, DatabaseManager, TableManager, ensureConnection, } from "./helpers";
5
- export * from "./postgres-event-store";
6
- export * from "./config";
7
- //# sourceMappingURL=index.d.ts.map
1
+ import { P as PostgresConfig, F as FromEnvOptions } from './helpers-vPAudN_S.js';
2
+ export { C as ClientCleanUp, a as ClientFactory, b as ClientWrapper, D as DatabaseManager, c as DefaultPostgresUnitOfWork, I as IsolationLevel, d as PoolOptions, e as PostgresTransactionOptions, f as PostgresUnitOfWork, T as TableManager, g as createPostgresUnitOfWork, h as ensureConnection } from './helpers-vPAudN_S.js';
3
+ import { Client, PoolClient } from 'pg';
4
+ import { EventStore, Message, StoredEvent, EventStoreFetchResult } from '@hexaijs/core';
5
+ import { ConfigSpec } from 'ezcfg';
6
+
7
+ declare class PostgresConfigSpec implements ConfigSpec<PostgresConfig> {
8
+ private readonly prefix;
9
+ private readonly mode;
10
+ readonly _type = "postgres";
11
+ constructor(prefix: string, mode?: FromEnvOptions["mode"]);
12
+ resolve(errors: string[]): PostgresConfig | undefined;
13
+ }
14
+ /**
15
+ * PostgreSQL database configuration from environment variables.
16
+ * Returns a PostgresConfig instance.
17
+ *
18
+ * @param prefix - Environment variable prefix
19
+ * @param mode - "url" reads {PREFIX}_URL, "fields" reads individual fields
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { defineConfig, env } from "@hexaijs/core";
24
+ * import { postgresConfig } from "@hexaijs/postgres";
25
+ *
26
+ * const getConfig = defineConfig({
27
+ * db: postgresConfig("ORDER_DB"), // reads ORDER_DB_URL
28
+ * db2: postgresConfig("PG", "fields"), // reads PG_HOST, PG_PORT, etc.
29
+ * });
30
+ *
31
+ * getConfig().db.host; // "localhost"
32
+ * getConfig().db.toString(); // "postgres://..."
33
+ * ```
34
+ */
35
+ declare function postgresConfig(prefix: string, mode?: FromEnvOptions["mode"]): PostgresConfigSpec;
36
+
37
+ interface MigrationOptions {
38
+ url: PostgresConfig | string;
39
+ dir: string;
40
+ namespace?: string;
41
+ direction?: "up" | "down";
42
+ count?: number;
43
+ dryRun?: boolean;
44
+ }
45
+ declare function runMigrations({ namespace, url, dir, direction, count, dryRun, }: MigrationOptions): Promise<void>;
46
+
47
+ declare function runHexaiMigrations(dbUrl: string | PostgresConfig): Promise<void>;
48
+
49
+ type PgClient = Client | PoolClient;
50
+ interface PostgresEventStoreConfig {
51
+ tableName?: string;
52
+ }
53
+ declare class PostgresEventStore implements EventStore {
54
+ private readonly client;
55
+ private readonly tableName;
56
+ constructor(client: PgClient, config?: PostgresEventStoreConfig);
57
+ store(event: Message): Promise<StoredEvent>;
58
+ storeAll(events: Message[]): Promise<StoredEvent[]>;
59
+ fetch(afterPosition: number, limit?: number): Promise<EventStoreFetchResult>;
60
+ getLastPosition(): Promise<number>;
61
+ private deserializeRow;
62
+ }
63
+
64
+ export { FromEnvOptions, type MigrationOptions, PostgresConfig, PostgresConfigSpec, PostgresEventStore, type PostgresEventStoreConfig, postgresConfig, runHexaiMigrations, runMigrations };