Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion packages/durable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@
"import": "./dist/testing/index.js",
"default": "./dist/testing/index.js"
},
"./package.json": "./package.json"
"./package.json": "./package.json",
"./storage/postgres": {
"source": "./src/storage/postgres/index.ts",
"types": "./dist/storage/postgres/index.d.ts",
"import": "./dist/storage/postgres/index.js"
}
},
"sideEffects": false,
"files": [
Expand Down Expand Up @@ -168,7 +173,9 @@
},
"devDependencies": {
"@cloudflare/vitest-plugin": "1.4.4",
"@types/pg": "8.23.1",
"fake-indexeddb": "6.2.6",
"pg": "8.23.0",
"shx": "0.3.4",
"vitest": "4.1.12"
}
Expand Down
67 changes: 67 additions & 0 deletions packages/durable/src/storage/postgres/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** Values the Postgres storage binds as query parameters. */
export type PostgresValue = null | boolean | number | string;

export interface PostgresQueryResult<R> {
readonly rows: R[];
readonly rowCount: number | null;
}

/** Anything that runs one parameterized statement, such as a pool or a checked-out client. */
export interface PostgresQueryable {
query<R extends object>(text: string, values?: PostgresValue[]): Promise<PostgresQueryResult<R>>;
}

/** A connection checked out of the pool. `release(error)` discards it instead of returning it. */
export interface PostgresPoolClient extends PostgresQueryable {
release(error?: Error | boolean): void;
on(event: "error", listener: (error: Error) => void): unknown;
off(event: "error", listener: (error: Error) => void): unknown;
}

/**
* The connection pool `PostgresStorage` runs on. A `pg.Pool` satisfies it structurally, so this package
* never imports a driver. The pool belongs to the caller: closing the storage does not end it, and an open
* storage holds no connection between operations.
*/
export interface PostgresPool extends PostgresQueryable {
connect(): Promise<PostgresPoolClient>;
}

/**
* Run `callback` in a transaction on its own pooled client. When BEGIN, COMMIT, or ROLLBACK fails, the client is
* discarded so the pool never reuses a connection in an unknown transaction state.
*/
export async function inTransaction<T>(
pool: PostgresPool,
begin: string,
callback: (client: PostgresQueryable) => Promise<T>,
): Promise<T> {
const client = await pool.connect();
let discard: Error | undefined;
const control = async (sql: string) => {
try {
await client.query(sql);
} catch (error) {
discard = error as Error;
throw error;
}
};
try {
await control(begin);
let result: T;
try {
result = await callback(client);
} catch (error) {
try {
await control("ROLLBACK");
} catch (rollbackError) {
throw new AggregateError([error, rollbackError], "Durable Postgres transaction failed to roll back");
}
throw error;
}
await control("COMMIT");
return result;
} finally {
client.release(discard);
}
}
15 changes: 15 additions & 0 deletions packages/durable/src/storage/postgres/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type {
PostgresPool,
PostgresPoolClient,
PostgresQueryable,
PostgresQueryResult,
PostgresValue,
} from "./database.ts";
export {
applyPostgresMigrations,
CURRENT_POSTGRES_SCHEMA_VERSION,
DEFAULT_POSTGRES_SCHEMA,
POSTGRES_MIGRATIONS,
type PostgresMigration,
} from "./migrations.ts";
export { PostgresStorage, type PostgresStorageOptions } from "./storage.ts";
Loading