Skip to content

Commit

Permalink
Reverted change to printWidth in .prettierrc
Browse files Browse the repository at this point in the history
  • Loading branch information
jawj committed Jan 28, 2025
1 parent 8187789 commit 675651b
Show file tree
Hide file tree
Showing 19 changed files with 344 additions and 138 deletions.
3 changes: 1 addition & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"singleQuote": true,
"printWidth": 100
"singleQuote": true
}
15 changes: 9 additions & 6 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ const sql = neon(process.env.DATABASE_URL);
const showLatestN = 10;

const [posts, tags] = await sql.transaction(
[sql`SELECT * FROM posts ORDER BY posted_at DESC LIMIT ${showLatestN}`, sql`SELECT * FROM tags`],
[
sql`SELECT * FROM posts ORDER BY posted_at DESC LIMIT ${showLatestN}`,
sql`SELECT * FROM tags`,
],
{
isolationLevel: 'RepeatableRead',
readOnly: true,
Expand All @@ -172,10 +175,9 @@ const [posts, tags] = await sql.transaction(
Or as an example of the function case:

```javascript
const [authors, tags] = await neon(process.env.DATABASE_URL).transaction((txn) => [
txn`SELECT * FROM authors`,
txn`SELECT * FROM tags`,
]);
const [authors, tags] = await neon(process.env.DATABASE_URL).transaction(
(txn) => [txn`SELECT * FROM authors`, txn`SELECT * FROM tags`],
);
```

The optional second argument to `transaction()`, `options`, has the same keys as the options to the ordinary query function -- `arrayMode`, `fullResults` and `fetchOptions` -- plus three additional keys that concern the transaction configuration. These transaction-related keys are: `isolationMode`, `readOnly` and `deferrable`. They are described below. Defaults for the transaction-related keys can also be set as options to the `neon` function.
Expand Down Expand Up @@ -267,7 +269,8 @@ If connecting to a non-Neon database, the `wsProxy` option should point to [your

```javascript
// either:
neonConfig.wsProxy = (host, port) => `my-wsproxy.example.com/v1?address=${host}:${port}`;
neonConfig.wsProxy = (host, port) =>
`my-wsproxy.example.com/v1?address=${host}:${port}`;
// or (with identical effect):
neonConfig.wsProxy = 'my-wsproxy.example.com/v1';
```
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ try {
await client.query('BEGIN');
const {
rows: [{ id: postId }],
} = await client.query('INSERT INTO posts (title) VALUES ($1) RETURNING id', ['Welcome']);
} = await client.query('INSERT INTO posts (title) VALUES ($1) RETURNING id', [
'Welcome',
]);
await client.query('INSERT INTO photos (post_id, url) VALUES ($1, $2)', [
postId,
's3.bucket/photo/url',
Expand Down
60 changes: 47 additions & 13 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ export class NeonClient extends Client {
crypto.subtle === undefined ||
crypto.subtle.importKey === undefined
) {
throw new Error('Cannot use SASL auth when `crypto.subtle` is not defined');
throw new Error(
'Cannot use SASL auth when `crypto.subtle` is not defined',
);
}

const cs = crypto.subtle;
Expand All @@ -125,7 +127,8 @@ export class NeonClient extends Client {

const attrPairs = Object.fromEntries(
serverData.split(',').map((attrValue) => {
if (!/^.=/.test(attrValue)) throw new Error('SASL: Invalid attribute pair entry');
if (!/^.=/.test(attrValue))
throw new Error('SASL: Invalid attribute pair entry');
const name = attrValue[0];
const value = attrValue.substring(2);
return [name, value];
Expand All @@ -137,17 +140,30 @@ export class NeonClient extends Client {
const iterationText = attrPairs.i;

if (!nonce || !/^[!-+--~]+$/.test(nonce))
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing/unprintable');
if (!salt || !/^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(salt))
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing/not base64');
throw new Error(
'SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing/unprintable',
);
if (
!salt ||
!/^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(
salt,
)
)
throw new Error(
'SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing/not base64',
);
if (!iterationText || !/^[1-9][0-9]*$/.test(iterationText))
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: missing/invalid iteration count');
throw new Error(
'SASL: SCRAM-SERVER-FIRST-MESSAGE: missing/invalid iteration count',
);
if (!nonce.startsWith(session.clientNonce))
throw new Error(
'SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce',
);
if (nonce.length === session.clientNonce.length)
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short');
throw new Error(
'SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short',
);

const iterations = parseInt(iterationText, 10);
const saltBytes = Buffer.from(salt, 'base64');
Expand All @@ -161,7 +177,11 @@ export class NeonClient extends Client {
['sign'],
);
let ui1 = new Uint8Array(
await cs.sign('HMAC', iterHmacKey, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])])),
await cs.sign(
'HMAC',
iterHmacKey,
Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]),
),
);
let ui = ui1;
for (var i = 0; i < iterations - 1; i++) {
Expand All @@ -177,14 +197,20 @@ export class NeonClient extends Client {
false,
['sign'],
);
const clientKey = new Uint8Array(await cs.sign('HMAC', ckHmacKey, enc.encode('Client Key')));
const clientKey = new Uint8Array(
await cs.sign('HMAC', ckHmacKey, enc.encode('Client Key')),
);
const storedKey = await cs.digest('SHA-256', clientKey);

const clientFirstMessageBare = 'n=*,r=' + session.clientNonce;
const serverFirstMessage = 'r=' + nonce + ',s=' + salt + ',i=' + iterations;
const clientFinalMessageWithoutProof = 'c=biws,r=' + nonce;
const authMessage =
clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof;
clientFirstMessageBare +
',' +
serverFirstMessage +
',' +
clientFinalMessageWithoutProof;

const csHmacKey = await cs.importKey(
'raw',
Expand All @@ -193,8 +219,12 @@ export class NeonClient extends Client {
false,
['sign'],
);
var clientSignature = new Uint8Array(await cs.sign('HMAC', csHmacKey, enc.encode(authMessage)));
var clientProofBytes = Buffer.from(clientKey.map((_, i) => clientKey[i] ^ clientSignature[i]));
var clientSignature = new Uint8Array(
await cs.sign('HMAC', csHmacKey, enc.encode(authMessage)),
);
var clientProofBytes = Buffer.from(
clientKey.map((_, i) => clientKey[i] ^ clientSignature[i]),
);
var clientProof = clientProofBytes.toString('base64');

const skHmacKey = await cs.importKey(
Expand All @@ -204,7 +234,11 @@ export class NeonClient extends Client {
false,
['sign'],
);
const serverKey = await cs.sign('HMAC', skHmacKey, enc.encode('Server Key'));
const serverKey = await cs.sign(
'HMAC',
skHmacKey,
enc.encode('Server Key'),
);
const ssbHmacKey = await cs.importKey(
'raw',
serverKey,
Expand Down
78 changes: 57 additions & 21 deletions src/httpQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export class NeonDbError extends Error {
constructor(message: string) {
super(message);

if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
if (
'captureStackTrace' in Error &&
typeof Error.captureStackTrace === 'function'
) {
Error.captureStackTrace(this, NeonDbError);
}
}
Expand Down Expand Up @@ -145,7 +148,10 @@ const errorFields = [
* pass as `fetchOptions` an object which will be merged into the options
* passed to `fetch`.
*/
export function neon<ArrayMode extends boolean = false, FullResults extends boolean = false>(
export function neon<
ArrayMode extends boolean = false,
FullResults extends boolean = false,
>(
connectionString: string,
{
arrayMode: neonOptArrayMode,
Expand Down Expand Up @@ -233,11 +239,13 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool

if (!Array.isArray(queries)) throw new Error(txnArgErrMsg);
queries.forEach((query) => {
if (query[Symbol.toStringTag] !== 'NeonQueryPromise') throw new Error(txnArgErrMsg);
if (query[Symbol.toStringTag] !== 'NeonQueryPromise')
throw new Error(txnArgErrMsg);
});

const parameterizedQueries = queries.map(
(query) => (query as NeonQueryPromise<ArrayMode, FullResults>).parameterizedQuery,
(query) =>
(query as NeonQueryPromise<ArrayMode, FullResults>).parameterizedQuery,
);
const opts = queries.map(
(query) => (query as NeonQueryPromise<ArrayMode, FullResults>).opts ?? {},
Expand Down Expand Up @@ -275,11 +283,15 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
...resolvedFetchOptions,
...txnOpts.fetchOptions,
};
if (txnOpts.arrayMode !== undefined) resolvedArrayMode = txnOpts.arrayMode;
if (txnOpts.fullResults !== undefined) resolvedFullResults = txnOpts.fullResults;
if (txnOpts.isolationLevel !== undefined) resolvedIsolationLevel = txnOpts.isolationLevel;
if (txnOpts.arrayMode !== undefined)
resolvedArrayMode = txnOpts.arrayMode;
if (txnOpts.fullResults !== undefined)
resolvedFullResults = txnOpts.fullResults;
if (txnOpts.isolationLevel !== undefined)
resolvedIsolationLevel = txnOpts.isolationLevel;
if (txnOpts.readOnly !== undefined) resolvedReadOnly = txnOpts.readOnly;
if (txnOpts.deferrable !== undefined) resolvedDeferrable = txnOpts.deferrable;
if (txnOpts.deferrable !== undefined)
resolvedDeferrable = txnOpts.deferrable;
}

// single query -- cannot be true at same time as `txnOpts !== undefined` above
Expand Down Expand Up @@ -342,7 +354,9 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
...resolvedFetchOptions, // this is last, so it gets the final say
});
} catch (err: any) {
const connectErr = new NeonDbError(`Error connecting to database: ${err}`);
const connectErr = new NeonDbError(
`Error connecting to database: ${err}`,
);
connectErr.sourceError = err;
throw connectErr;
}
Expand All @@ -354,9 +368,12 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
// batch query
const resultArray = rawResults.results;
if (!Array.isArray(resultArray))
throw new NeonDbError('Neon internal error: unexpected result format');
throw new NeonDbError(
'Neon internal error: unexpected result format',
);
return resultArray.map((result, i) => {
let sqlOpts = (allSqlOpts as HTTPQueryOptions<ArrayMode, FullResults>[])[i] ?? {};
let sqlOpts =
(allSqlOpts as HTTPQueryOptions<ArrayMode, FullResults>[])[i] ?? {};
let arrayMode = sqlOpts.arrayMode ?? resolvedArrayMode;
let fullResults = sqlOpts.fullResults ?? resolvedFullResults;
return processQueryResult(result, {
Expand All @@ -369,7 +386,8 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
});
} else {
// single query
let sqlOpts = (allSqlOpts as HTTPQueryOptions<ArrayMode, FullResults>) ?? {};
let sqlOpts =
(allSqlOpts as HTTPQueryOptions<ArrayMode, FullResults>) ?? {};
let arrayMode = sqlOpts.arrayMode ?? resolvedArrayMode;
let fullResults = sqlOpts.fullResults ?? resolvedFullResults;
return processQueryResult(rawResults, {
Expand All @@ -385,7 +403,8 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
if (status === 400) {
const json = (await response.json()) as any;
const dbError = new NeonDbError(json.message);
for (const field of errorFields) dbError[field] = json[field] ?? undefined;
for (const field of errorFields)
dbError[field] = json[field] ?? undefined;
throw dbError;
} else {
const text = await response.text();
Expand All @@ -397,18 +416,26 @@ export function neon<ArrayMode extends boolean = false, FullResults extends bool
return resolve as any;
}

function createNeonQueryPromise<ArrayMode extends boolean, FullResults extends boolean>(
execute: (pq: ParameterizedQuery, hqo?: HTTPQueryOptions<ArrayMode, FullResults>) => Promise<any>,
function createNeonQueryPromise<
ArrayMode extends boolean,
FullResults extends boolean,
>(
execute: (
pq: ParameterizedQuery,
hqo?: HTTPQueryOptions<ArrayMode, FullResults>,
) => Promise<any>,
parameterizedQuery: ParameterizedQuery,
opts?: HTTPQueryOptions<ArrayMode, FullResults>,
) {
return {
[Symbol.toStringTag]: 'NeonQueryPromise',
parameterizedQuery,
opts,
then: (resolve: any, reject: any) => execute(parameterizedQuery, opts).then(resolve, reject),
then: (resolve: any, reject: any) =>
execute(parameterizedQuery, opts).then(resolve, reject),
catch: (reject: any) => execute(parameterizedQuery, opts).catch(reject),
finally: (finallyFn: any) => execute(parameterizedQuery, opts).finally(finallyFn),
finally: (finallyFn: any) =>
execute(parameterizedQuery, opts).finally(finallyFn),
} as NeonQueryPromise<ArrayMode, FullResults>;
}

Expand All @@ -424,19 +451,26 @@ function processQueryResult(
) {
const types = new TypeOverrides(customTypes);
const colNames = rawResults.fields.map((field: any) => field.name);
const parsers = rawResults.fields.map((field: any) => types.getTypeParser(field.dataTypeID));
const parsers = rawResults.fields.map((field: any) =>
types.getTypeParser(field.dataTypeID),
);

// now parse and possibly restructure the rows data like node-postgres does
const rows =
arrayMode === true
? // maintain array-of-arrays structure
rawResults.rows.map((row: any) =>
row.map((col: any, i: number) => (col === null ? null : parsers[i](col))),
row.map((col: any, i: number) =>
col === null ? null : parsers[i](col),
),
)
: // turn into an object
rawResults.rows.map((row: any) => {
return Object.fromEntries(
row.map((col: any, i: number) => [colNames[i], col === null ? null : parsers[i](col)]),
row.map((col: any, i: number) => [
colNames[i],
col === null ? null : parsers[i](col),
]),
);
});

Expand All @@ -458,7 +492,9 @@ function processQueryResult(
return rows;
}

async function getAuthToken(authToken: HTTPQueryOptions<false, false>['authToken']) {
async function getAuthToken(
authToken: HTTPQueryOptions<false, false>['authToken'],
) {
if (typeof authToken === 'string') {
return authToken;
}
Expand Down
Loading

0 comments on commit 675651b

Please sign in to comment.