Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@embracesql/postgres

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@embracesql/postgres - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

6

package.json
{
"name": "@embracesql/postgres",
"version": "0.1.2",
"version": "0.1.3",
"description": "EmbraceSQL shared library for talking to postgres. Used in Node.",

@@ -13,3 +13,3 @@ "type": "module",

"dependencies": {
"@embracesql/shared": "^0.1.2",
"@embracesql/shared": "^0.1.3",
"glob": "^10.3.10",

@@ -23,3 +23,3 @@ "object-hash": "^3.0.0",

},
"gitHead": "8f9395afff4f331cde0761a283fea630c2d43a4f"
"gitHead": "edeebbd5811d60be38bba6c04df81b7013349f1a"
}

@@ -207,3 +207,3 @@ import { PGIndexes } from "./generator/pgtype/pgindex";

i,
context.database.resolveType(a.type)!,
context.database.resolveType(a.type),
true,

@@ -232,3 +232,3 @@ true,

i,
context.database.resolveType(a)!,
context.database.resolveType(a),
true,

@@ -310,2 +310,8 @@ false,

transform: { undefined: null },
// let's be nearly stateless on connections and not leave
// them in the pool for long by default -- this better deals
// with transient network errors -- avoids 'torn' connections in the pool
idle_timeout: 30,
max_lifetime: 30,
...(props ?? {}),
});

@@ -312,0 +318,0 @@

@@ -117,3 +117,3 @@ import { Context, PostgresProcTypecast } from "../../../context";

.forEach((oid, i) => {
const type = context.database.resolveType(oid)!;
const type = context.database.resolveType(oid);
new AttributeNode(

@@ -241,3 +241,3 @@ parametersType,

.map((oid, i) => {
const type = context.database.resolveType(oid)!;
const type = context.database.resolveType(oid);
return {

@@ -244,0 +244,0 @@ name: this.proc.proc.proargnames[i],

@@ -24,3 +24,3 @@ import { PGCatalogType } from "./pgcatalogtype";

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const type = context.database.resolveType(this.rngsubtype)!;
const type = context.database.resolveType(this.rngsubtype);
// array with two elements is the nearst-to-a-tuple in JS

@@ -27,0 +27,0 @@ return `[${type.typescriptNamespacedName}, ${type.typescriptNamespacedName}]`;

import { Context } from "./context";
import databaseRole from "./middleware/role";
import { MiddlewareContext, MiddlewareDispatcher } from "./middleware/types";
import { EmbraceSQLInvocation, EmbraceSQLOptions } from "@embracesql/shared";
import {
EmbraceSQLInvocation,
EmbraceSQLOptions,
InvokeQueryOptions,
sleep,
} from "@embracesql/shared";
import postgres from "postgres";

@@ -23,3 +28,5 @@

/**
* A single postgres database. Inherit from this in generated code.
* A single postgres database.
*
* Code generation extends this class, making it specific to your database.
*/

@@ -36,9 +43,9 @@ export abstract class PostgresDatabase<

/**
* Clean up the connection.
* Call when you are all finished talking to the database.
*/
public async disconnect() {
await this.context.sql.end();
await this.context.sql.end({ timeout: 1 });
}
get cls(): ConstructorOf<this> {
private get cls(): ConstructorOf<this> {
const current = Object.getPrototypeOf(this).constructor;

@@ -48,6 +55,2 @@ return current;

get self(): this {
return this;
}
get typed(): TTypecast {

@@ -62,3 +65,4 @@ return this.context.sql.typed as unknown as TTypecast;

async beginTransaction() {
const myself = this.self;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const myself = this;
const CurrentSubclass = this.cls;

@@ -71,13 +75,19 @@ return await new Promise<{

const complete = new Promise((resolve, reject) => {
this.context.sql
.begin(async (sql) => {
resolveReady({
// this is 'the change' -- adding in a new sql that is in transaction
database: new CurrentSubclass({ ...this.context, sql }),
commit: () => resolve(true),
rollback: (message?: string) => reject(message),
});
await complete;
})
.catch((reason) => reason);
// postgres doesn't really have 'nested' transactions -- it has savepoints
// so check if we are 'in' a transaction
const begin =
this.context.sql.begin?.bind(this.context.sql) ??
(this.context.sql as postgres.TransactionSql).savepoint.bind(
this.context.sql,
);
begin(async (sql) => {
resolveReady({
// this is 'the change' -- adding in a new sql that is in transaction
database: new CurrentSubclass({ ...this.context, sql }),
commit: () => resolve(true),
rollback: (message?: string) => reject(message),
});
await complete;
}).catch((reason) => reason);
});

@@ -94,16 +104,20 @@ });

async withTransaction<T>(body: (database: this) => Promise<T>) {
const CurrentSubclass = this.cls;
if (this.context.sql.begin !== undefined) {
// root transaction
return await this.context.sql.begin(
async (sql) =>
await body(new CurrentSubclass({ ...this.context, sql })),
);
} else {
// nested transaction
const nested = this.context.sql as postgres.TransactionSql;
return await nested.savepoint(
async (sql) =>
await body(new CurrentSubclass({ ...this.context, sql })),
);
const { database, commit, rollback } = await this.beginTransaction();
try {
const ret = await body(database);
commit();
return ret;
} catch (e) {
// the postgres driver has a flaw - if you encounter a fatal connection
// error inside a transaction, such that `rollback` cannot be run
// because the connection is dead -- the driver hangs awaiting a `rollback`
// that simply cannot happen
if ((e as postgres.PostgresError)?.severity === "FATAL") {
// the error is the return value -- yeah a little odd
// but this gets us past the rollback that won't roll back
throw e;
} else {
rollback();
throw e;
}
}

@@ -126,3 +140,3 @@ }

V = never,
O extends EmbraceSQLOptions = EmbraceSQLOptions,
O extends InvokeQueryOptions = InvokeQueryOptions,
>(

@@ -133,4 +147,6 @@ queryCallback: InvokeQuery<R, P, V, O>,

): Promise<R> {
// if retries are not specific -- now is the time
// here is the middleware run stack
const runStack = async (database: this) => {
const runStack = async (database: this, retry: number) => {
// pick up the headers

@@ -146,2 +162,3 @@ // first the middleware

>,
retry,
});

@@ -151,15 +168,30 @@ return queryCallback(database.context.sql, request);

// need a reserved or single connection throughout
// so that all effects are applied to the same session
if (this.context.sql.begin !== undefined) {
return this.withTransaction(async (database) => {
return runStack(database);
}) as R;
} else {
// if we are in a transaction, there won't be an option to reserve
// but the good news is the driver has already reserved a connection
// for the scope of the transaction
return runStack(this);
let finalError: Error | undefined = undefined;
for (let retry = 0; retry <= (request?.options?.retries ?? 0); retry += 1) {
try {
// need a reserved or single connection throughout
// so that all effects are applied to the same session
if (this.context.sql.begin !== undefined) {
// make sure to await here -- need to catch what might be thrown
return (await this.withTransaction(async (database) => {
return await runStack(database, retry);
})) as R;
} else {
// if we are in a transaction, there won't be an option to reserve
// but the good news is the driver has already reserved a connection
// for the scope of the transaction
return await runStack(this, retry);
}
} catch (e) {
finalError = e as Error;
// this is the exponential backoff part
if (retry > 0) {
await sleep(100 * 2 ** retry);
}
}
}
// if we got here, we're past all the retries and it is time to throw
throw finalError;
}
}

@@ -8,3 +8,11 @@ import { Context } from "../context";

export type MiddlewareContext = Context & {
/**
* Request for a query. Modify this with the middleware chain and
* the final modified version will be used to invoke your actual query.
*/
request?: EmbraceSQLInvocation<object, object, InvokeQueryOptions>;
/**
* Current retry with 0 indicating we haven't retried ... yet.
*/
retry: number;
};

@@ -11,0 +19,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc